@clocknext/sdk 0.7.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,7 +41,16 @@ await cnk.flush();
41
41
  ## Recording signals
42
42
 
43
43
  There are three signal types, matching ClockNext's billable primitives. Each has
44
- a typed helper that enforces the right required fields at compile time.
44
+ a typed helper that enforces the right required fields at compile time, and each
45
+ posts to **its own route** — `/api/v1/signal/credit`, `/signal/wallet`,
46
+ `/signal/outcome`. The signal's `type` is what picks that route; from 0.10 it is
47
+ no longer sent in the body.
48
+
49
+ > **Migrating from 0.9 or earlier?** Your calling code does not change — same
50
+ > methods, same arguments. What changed is the wire: the SDK now targets the
51
+ > `/api/v1/signal/*` family instead of the deprecated `POST /api/v1/usage`, and
52
+ > the `metric` field is now `composite` (see [Composites](#composites)). Bump the
53
+ > package and you are migrated.
45
54
 
46
55
  `model` is always the **catalog model id** enabled in your org (matched
47
56
  case-insensitively against `OrgModel.modelId`). Credits and outcomes are
@@ -76,23 +85,54 @@ await cnk.signals.outcome({ customerId, model, agentKey: "resolved_ticket.verify
76
85
  await cnk.signals.outcome({ customerId, model, agentKey: "resolved_ticket.resolve", runId, complete: true, tokens: { input: 500, output: 200 } });
77
86
  ```
78
87
 
79
- ### Pricing metrics
88
+ ### Composites
80
89
 
81
- Tag any credit or outcome signal with a **pricing metric** for reporting. `ref`
82
- is the metric's ref ID (Settings → Pricing metrics) and `value` is your own
83
- correlation id; the metric rolls usage up one row per distinct value.
90
+ A **composite** bundles several credits / outcomes / units and is priced and
91
+ billed as one thing. Tag a signal with `composite` to group it into one
92
+ *occurrence* — `ref` is the composite's ref ID and `value` is your own
93
+ correlation id for that occurrence (a session, a call, a job):
84
94
 
85
95
  ```ts
86
96
  await cnk.signals.credit({
87
97
  customerId, model: "llama-3.3-70b-versatile", agentKey: "api_credit",
88
98
  tokens: { input: 1200, output: 340 },
89
- metric: { ref: "voice_ai", value: "call_5f3a" },
99
+ composite: { ref: "voice_ai", value: "call_5f3a" },
90
100
  });
91
101
  ```
92
102
 
93
- Metric tags are **reporting only** — they never change what's billed, a tag that
94
- names no live metric is silently ignored, and a bad tag never fails the signal.
95
- Tags on wallet signals are ignored.
103
+ An occurrence opens on the first tagged signal and costs nothing. **Closing it
104
+ is what bills it** — one charge for the bundle, however many signals it
105
+ contained:
106
+
107
+ ```ts
108
+ await cnk.signals.completeComposite({
109
+ customerId,
110
+ composite: { ref: "voice_ai", value: "call_5f3a" },
111
+ });
112
+ ```
113
+
114
+ Only your code knows when the work ended, so closing is a declaration, not
115
+ something ClockNext infers from which signals have arrived: tagged signals may
116
+ arrive in any order and the occurrence stays open and unbilled until you say
117
+ otherwise. Put the call on the line before your own `return`.
118
+
119
+ An **outcome never needs it** — its own `complete: true` closes its run and the
120
+ matching occurrence together. Credits and units have no "done" signal of their
121
+ own, so this is theirs.
122
+
123
+ Closing twice is safe (`ALREADY_COMPLETED`, and it cannot bill again), and so is
124
+ closing *early* — before every entitlement in the bundle has reported, or while
125
+ a prepaid slot pool is full: the server parks the request and applies it when
126
+ the blocker clears, rather than failing it.
127
+
128
+ A tag that names no live composite is ignored and never fails the signal — the
129
+ signal carries money, the tag only decides what it is grouped under. Tags on
130
+ wallet signals are ignored.
131
+
132
+ > **Renamed in 0.10.** This field was called `metric` and was documented as
133
+ > "reporting only, never changes what's billed". That was wrong: where the plan
134
+ > sells the composite, the occurrence is the billable unit. Rename `metric:` to
135
+ > `composite:` — the shape (`{ ref, value }`) is unchanged.
96
136
 
97
137
  ### Async vs sync
98
138
 
@@ -102,21 +142,31 @@ buffer on a size/interval trigger. This keeps billing telemetry off your LLM hot
102
142
  path.
103
143
 
104
144
  > **"Batching" here means timing, not a bulk request.** Each signal is still its
105
- > own `POST /api/v1/usage`; the flusher just groups *when* sends happen and caps
106
- > how many run at once (`batch.maxConcurrency`). There is no single bulk-ingest
107
- > call, so size your request budget per signal.
145
+ > own POST to its own `/api/v1/signal/*` route; the flusher just groups *when*
146
+ > sends happen and caps how many run at once (`batch.maxConcurrency`). There is
147
+ > no single bulk-ingest call, so size your request budget per signal.
108
148
 
109
- To get the computed result back (and any real-time allowance rejection), either:
149
+ To await the request itself (rather than buffering it), either:
110
150
 
111
151
  ```ts
112
152
  // per call
113
153
  const res = await cnk.signals.credit({ … }, { wait: true });
114
- console.log(res.usageLog);
115
154
 
116
155
  // or globally
117
156
  const cnk = new ClockNext({ apiKey, mode: "sync" });
118
157
  ```
119
158
 
159
+ > **Awaiting is not the same as billed.** Where the server has a durable queue
160
+ > configured it answers `202` and prices the signal in a worker, so you get
161
+ > `{ queued: true, messageId, usageLog: null }` — accepted, not yet recorded, and
162
+ > validated only later (a bad `agentKey` is dropped there, silently). Only when
163
+ > the server prices inline do you get `{ queued: false, usageLog }`. Branch on
164
+ > `queued`; never assume `usageLog` is populated.
165
+ >
166
+ > To validate and price a signal synchronously, use `signals.verify()` (a dry
167
+ > run — it always returns the projected log). To confirm a real signal landed,
168
+ > read it back with `signals.list({ customerId })`.
169
+
120
170
  > In `async` mode, server-side rejections (e.g. insufficient allowance, `422`)
121
171
  > surface via the `onError` hook or `flush()`, not from the `track()` call.
122
172
 
@@ -150,7 +200,7 @@ const cnk = new ClockNext({ apiKey, mode: "sync" });
150
200
  const c = await cnk.customers.create({ name: "Acme", email: "ops@acme.com" });
151
201
  const one = await cnk.customers.get(c.id);
152
202
  const page = await cnk.customers.list({ limit: 50, q: "ac" });
153
- await cnk.customers.update(c.id, { notes: "VIP" });
203
+ await cnk.customers.update(c.id, { legalName: "Acme Inc." });
154
204
  await cnk.customers.delete(c.id);
155
205
 
156
206
  // iterate every customer (auto-follows the cursor)
@@ -166,7 +216,7 @@ await cnk.customers.revenue(c.id, { range: "30d" });
166
216
  ### Members
167
217
 
168
218
  ```ts
169
- await cnk.customers.addMember(c.id, { name: "Alice", role: "ADMIN", email: "alice@acme.com" });
219
+ await cnk.customers.addMember(c.id, { name: "Alice", email: "alice@acme.com" });
170
220
  const members = await cnk.customers.listMembers(c.id);
171
221
  await cnk.customers.updateMember(c.id, members[0].id, { name: "Alice R." });
172
222
  await cnk.customers.removeMember(c.id, members[0].id);
@@ -230,9 +280,12 @@ Define the billable *types*; record usage against them with `signals.*`.
230
280
  await cnk.credits.create({ name: "API", agentKey: "api_credit", basePrice: 1, marginPercent: 20, pricePerCredit: 0.01 });
231
281
  await cnk.credits.list({ active: true });
232
282
 
233
- // outcome type — a multi-step workflow; each step has its own agentKey
283
+ // outcome type — a multi-step workflow. TWO kinds of agentKey, both required and
284
+ // both unique per org: the outcome's own (its identity) and each step's (what
285
+ // signals.outcome() reports against). They are separate namespaces.
234
286
  await cnk.outcomes.create({
235
287
  name: "Resolved ticket",
288
+ agentKey: "resolved_ticket",
236
289
  basePrice: 1, marginPercent: 20, pricePerOutcome: 2,
237
290
  steps: [{ name: "Triage", agentKey: "resolved_ticket.triage", basePrice: 0.5 }],
238
291
  });
@@ -243,13 +296,35 @@ await cnk.units.create({ name: "Seats", agentKey: "seats", pricingType: "FLAT",
243
296
 
244
297
  ### Unit metering
245
298
 
246
- Record a unit event (no tokens) and read a customer's unit balances:
299
+ Record a unit event (no tokens, no model) and read a customer's unit balances:
247
300
 
248
301
  ```ts
249
302
  await cnk.signals.unit({ customerId: c.id, agentKey: "seats" });
303
+
304
+ // several at once, instead of N separate calls
305
+ await cnk.signals.unit({ customerId: c.id, agentKey: "seats", quantity: 3 });
306
+
307
+ // part of a composite, and safe to retry
308
+ await cnk.signals.unit({
309
+ customerId: c.id, agentKey: "seats",
310
+ composite: { ref: "voice_ai", value: "call_5f3a" },
311
+ idempotencyKey: `seats:${requestId}`,
312
+ });
313
+
250
314
  await cnk.signals.unitUsage({ customerId: c.id });
251
315
  ```
252
316
 
317
+ `quantity` defaults to 1. On a prepaid (ADVANCE) plan the draw is
318
+ all-or-nothing: asking for more than remains is refused in full rather than
319
+ partially filled.
320
+
321
+ > **Changed in 0.10.** `signals.unit()` moved to `POST /api/v1/signal/unit`,
322
+ > which is **durable** (the report is written down before it is priced, so a
323
+ > failure parks for retry instead of losing a billable event) and **idempotent**
324
+ > (on the old route a retried report drew the balance down twice). Because it can
325
+ > now answer `202`, this resolves `{ queued, unitUsage, rawId }` rather than a
326
+ > bare `{ unitUsage }` — branch on `queued`, don't assume `unitUsage`.
327
+
253
328
  ## Webhooks
254
329
 
255
330
  Threshold alerts — at most one per kind (`CREDIT` / `OUTCOME` / `WALLET`):
package/dist/index.cjs CHANGED
@@ -121,7 +121,8 @@ var Flusher = class {
121
121
  try {
122
122
  await this.transport.request({
123
123
  method: "POST",
124
- path: "/api/v1/usage",
124
+ // Per-item, not per-Flusher: one route per meter. See `QueuedSignal`.
125
+ path: item.path,
125
126
  body: item.body,
126
127
  // Best-effort metering opts into retries on transient failures.
127
128
  retry: true
@@ -229,7 +230,7 @@ function sleep(ms) {
229
230
  }
230
231
 
231
232
  // src/http.ts
232
- var SDK_VERSION = "0.7.0" ;
233
+ var SDK_VERSION = "0.10.0" ;
233
234
  var Transport = class {
234
235
  constructor(cfg) {
235
236
  this.cfg = cfg;
@@ -324,6 +325,36 @@ var Transport = class {
324
325
  }
325
326
  };
326
327
 
328
+ // src/resources/composites.ts
329
+ var Composites = class {
330
+ constructor(transport) {
331
+ this.transport = transport;
332
+ }
333
+ transport;
334
+ /** List composites. Pass `{ active: true }` for only the ones a signal can
335
+ * still be tagged with — the default list INCLUDES archived ones, which
336
+ * keep every row they already own. */
337
+ async list(params = {}) {
338
+ const res = await this.transport.request({
339
+ method: "GET",
340
+ path: "/api/v1/composites",
341
+ query: {
342
+ active: params.active === void 0 ? void 0 : String(params.active)
343
+ }
344
+ });
345
+ return res.composites;
346
+ }
347
+ /** Create a composite. `POST /api/v1/composites`. */
348
+ async create(input) {
349
+ const res = await this.transport.request({
350
+ method: "POST",
351
+ path: "/api/v1/composites",
352
+ body: input
353
+ });
354
+ return res.composite;
355
+ }
356
+ };
357
+
327
358
  // src/resources/credits.ts
328
359
  var Credits = class {
329
360
  constructor(transport) {
@@ -506,7 +537,7 @@ var Customers = class {
506
537
  });
507
538
  return res.members;
508
539
  }
509
- /** Add a member to a customer. Duplicate `employeeId` → ConflictError. */
540
+ /** Add a member to a customer. Duplicate `email` → ConflictError. */
510
541
  async addMember(id, input) {
511
542
  const res = await this.transport.request({
512
543
  method: "POST",
@@ -515,7 +546,7 @@ var Customers = class {
515
546
  });
516
547
  return res.member;
517
548
  }
518
- /** Update a member's profile (role is fixed). `PATCH …/:id/members/:memberId`. */
549
+ /** Update a member's profile. Duplicate `email` → ConflictError. `PATCH …/:id/members/:memberId`. */
519
550
  async updateMember(id, memberId, input) {
520
551
  const res = await this.transport.request({
521
552
  method: "PATCH",
@@ -524,12 +555,13 @@ var Customers = class {
524
555
  });
525
556
  return res.member;
526
557
  }
527
- /** Remove a member from a customer. `DELETE …/:id/members/:memberId`. */
558
+ /** Remove a member from a customer, returning who was removed. `DELETE …/:id/members/:memberId`. */
528
559
  async removeMember(id, memberId) {
529
- await this.transport.request({
560
+ const res = await this.transport.request({
530
561
  method: "DELETE",
531
562
  path: `/api/v1/customers/${encodeURIComponent(id)}/members/${encodeURIComponent(memberId)}`
532
563
  });
564
+ return res.member;
533
565
  }
534
566
  // --- Wallet & billing config --------------------------------------------
535
567
  /** The customer's wallet balance + transactions. `GET …/:id/wallet`. */
@@ -855,18 +887,28 @@ function newKey() {
855
887
  }
856
888
 
857
889
  // src/resources/signals.ts
890
+ var SIGNAL_PATHS = {
891
+ credit: "/api/v1/signal/credit",
892
+ outcome: "/api/v1/signal/outcome",
893
+ wallet: "/api/v1/signal/wallet"
894
+ };
895
+ var UNIT_PATH = "/api/v1/signal/unit";
896
+ var COMPOSITE_COMPLETE_PATH = "/api/v1/signal/composite/complete";
858
897
  function signalToWire(signal) {
898
+ const t = signal.tokens;
859
899
  const body = {
860
900
  customerId: signal.customerId,
861
- type: signal.type,
862
- model: signal.model
901
+ // `usage` is required on all three model-priced kinds. input/output are
902
+ // always sent; `cacheTokens` only when provided (the server defaults 0).
903
+ usage: {
904
+ model: signal.model,
905
+ inputTokens: t.input,
906
+ outputTokens: t.output,
907
+ ...t.cache != null ? { cacheTokens: t.cache } : {}
908
+ }
863
909
  };
864
910
  if (signal.member) body.member = signal.member;
865
911
  if (signal.custom) body.custom = signal.custom;
866
- const t = signal.tokens;
867
- body.inputTokens = t.input;
868
- body.outputTokens = t.output;
869
- if (t.cache != null) body.cacheTokens = t.cache;
870
912
  if (signal.type === "credit" || signal.type === "outcome") {
871
913
  body.agentKey = signal.agentKey;
872
914
  }
@@ -874,12 +916,18 @@ function signalToWire(signal) {
874
916
  body.runId = signal.runId;
875
917
  if (signal.complete != null) body.complete = signal.complete;
876
918
  }
877
- if (signal.type !== "wallet" && signal.metric) {
878
- body[signal.metric.ref] = signal.metric.value;
919
+ if (signal.type !== "wallet" && signal.composite) {
920
+ body.composite = { [signal.composite.ref]: signal.composite.value };
879
921
  }
880
922
  if (signal.idempotencyKey) body.idempotencyKey = signal.idempotencyKey;
881
923
  return body;
882
924
  }
925
+ function handles(res) {
926
+ return {
927
+ ...res.messageId != null ? { messageId: res.messageId } : {},
928
+ ...res.rawId != null ? { rawId: res.rawId } : {}
929
+ };
930
+ }
883
931
  var Signals = class {
884
932
  constructor(transport, flusher, cfg) {
885
933
  this.transport = transport;
@@ -889,56 +937,79 @@ var Signals = class {
889
937
  transport;
890
938
  flusher;
891
939
  cfg;
940
+ /**
941
+ * Record one usage signal, to the route its `type` selects:
942
+ * `POST /api/v1/signal/credit` | `/signal/outcome` | `/signal/wallet`.
943
+ *
944
+ * Returns as soon as the signal is buffered (async mode) or the request has
945
+ * been answered (`{ wait: true }` / sync mode). See {@link TrackResult}: a
946
+ * resolved promise means "accepted", which is not the same as "billed".
947
+ */
892
948
  async track(signal, opts = {}) {
893
949
  const body = signalToWire(signal);
894
950
  if (body.idempotencyKey == null) body.idempotencyKey = newKey();
951
+ const path = SIGNAL_PATHS[signal.type];
895
952
  const sendNow = opts.wait || this.cfg.mode === "sync";
896
953
  if (!sendNow) {
897
- this.flusher.enqueue({ body, signal });
954
+ this.flusher.enqueue({ path, body, signal });
898
955
  return { queued: true };
899
956
  }
900
957
  const res = await this.transport.request({
901
958
  method: "POST",
902
- path: "/api/v1/usage",
959
+ path,
903
960
  body,
904
961
  // Best-effort metering opts into retries on transient failures.
905
962
  retry: true
906
963
  });
907
964
  return {
908
- queued: false,
909
- usageLog: res.usageLog ?? null
965
+ queued: res.queued === true,
966
+ usageLog: res.usageLog ?? null,
967
+ ...handles(res)
910
968
  };
911
969
  }
912
970
  /**
913
971
  * Validate + price a signal WITHOUT recording it (server `dryRun`). Returns
914
972
  * the projected usage log so an integration can preflight before sending real
915
973
  * traffic — no inbox row, wallet / outcome writes, or webhooks fire.
974
+ *
975
+ * Only the model-priced kinds support this. A dry run is not offered for unit
976
+ * reports or composite completions, and the server REJECTS the flag on those
977
+ * routes rather than accepting and ignoring it — which is why there is no
978
+ * dry-run path through `unit()` or `completeComposite()`.
916
979
  */
917
980
  async verify(signal) {
918
981
  const body = signalToWire(signal);
919
982
  body.dryRun = true;
920
983
  const res = await this.transport.request({
921
984
  method: "POST",
922
- path: "/api/v1/usage",
985
+ path: SIGNAL_PATHS[signal.type],
923
986
  body,
924
987
  // A dry run writes nothing, so it is always safe to retry.
925
988
  retry: true
926
989
  });
927
990
  return res.usageLog ?? null;
928
991
  }
929
- /** Meter a named credit. */
992
+ /** Meter a named credit. `POST /api/v1/signal/credit`. */
930
993
  credit(input, opts) {
931
994
  return this.track({ ...input, type: "credit" }, opts);
932
995
  }
933
- /** Debit the customer's wallet at model cost. */
996
+ /** Debit the customer's wallet at model cost. `POST /api/v1/signal/wallet`. */
934
997
  wallet(input, opts) {
935
998
  return this.track({ ...input, type: "wallet" }, opts);
936
999
  }
937
- /** Advance one step of an outcome workflow. */
1000
+ /** Advance one step of an outcome workflow. `POST /api/v1/signal/outcome`. */
938
1001
  outcome(input, opts) {
939
1002
  return this.track({ ...input, type: "outcome" }, opts);
940
1003
  }
941
- /** Recent usage logs + totals for a customer. `GET /api/v1/usage`. */
1004
+ /**
1005
+ * Recent usage logs + totals for a customer. `GET /api/v1/usage`.
1006
+ *
1007
+ * Still the legacy path, deliberately. The `/api/v1/signal/*` family is
1008
+ * POST-only — it replaced how usage is INGESTED, not how it is read — so
1009
+ * `GET /api/v1/usage` has no successor and is not deprecated. It is also the
1010
+ * only way to prove a signal landed, which matters more now that ingestion
1011
+ * answers `202` for everything.
1012
+ */
942
1013
  async list(params) {
943
1014
  const res = await this.transport.request({
944
1015
  method: "GET",
@@ -950,22 +1021,94 @@ var Signals = class {
950
1021
  /**
951
1022
  * Record one unit-metered event (a fixed-price meter with no tokens),
952
1023
  * addressed to a catalog unit by its `agentKey`. Sent immediately — unit
953
- * events are not buffered. `POST /api/v1/units`.
1024
+ * events are not buffered. `POST /api/v1/signal/unit`.
1025
+ *
1026
+ * Two things changed with the move off `POST /api/v1/units`, and both show up
1027
+ * in the return type. The route is now durable (the report lands in an inbox
1028
+ * before it is priced, so a failure parks as Pending and can be retried
1029
+ * instead of losing a billable event) and it answers `202` when it queues —
1030
+ * so this resolves {@link UnitTrackResult}, not a bare `{ unitUsage }`.
1031
+ * Branch on `queued`.
954
1032
  */
955
1033
  async unit(input) {
956
- return this.transport.request({
1034
+ const res = await this.transport.request({
957
1035
  method: "POST",
958
- path: "/api/v1/units",
1036
+ path: UNIT_PATH,
959
1037
  body: {
960
1038
  customerId: input.customerId,
961
1039
  agentKey: input.agentKey,
962
- ...input.member ? { member: input.member } : {}
1040
+ ...input.quantity != null ? { quantity: input.quantity } : {},
1041
+ ...input.member ? { member: input.member } : {},
1042
+ ...input.composite ? { composite: { [input.composite.ref]: input.composite.value } } : {},
1043
+ // Assigned HERE, before the send, so the SAME key rides every
1044
+ // transport-level retry below. Without it a single 500 followed by a
1045
+ // success is TWO un-keyed reports, and the server dedups on the key —
1046
+ // so it bills the allowance down twice, up to `retry.maxAttempts`
1047
+ // times. The route gained idempotency precisely to stop that; not
1048
+ // using it by default would leave the double-charge reachable from the
1049
+ // client instead of the server.
1050
+ idempotencyKey: input.idempotencyKey ?? newKey()
963
1051
  },
964
1052
  // Best-effort metering opts into retries on transient failures.
965
1053
  retry: true
966
1054
  });
1055
+ return {
1056
+ queued: res.queued === true,
1057
+ unitUsage: res.unitUsage ?? null,
1058
+ ...handles(res)
1059
+ };
967
1060
  }
968
- /** A customer's unit balances + recent unit events. `GET /api/v1/units`. */
1061
+ /**
1062
+ * Close ONE occurrence of a composite — a distinct tag value.
1063
+ * `POST /api/v1/signal/composite/complete`.
1064
+ *
1065
+ * A composite bundles several credits / outcomes / units and is billed as one
1066
+ * thing. The occurrence opens on the first tagged signal and costs nothing;
1067
+ * this call is the moment it is billed. Only your code knows when the
1068
+ * workflow ended, so this is a DECLARATION, not a hint: tagged signals may
1069
+ * arrive in any order and the occurrence stays open and unbilled until you
1070
+ * say otherwise. Put it on the line before your own `return`.
1071
+ *
1072
+ * An OUTCOME never needs this — its own `complete: true` closes its run and
1073
+ * the matching occurrence together. Credits and units have no "done" signal
1074
+ * of their own, so this is theirs.
1075
+ *
1076
+ * Closing twice is safe: the second call answers `ALREADY_COMPLETED` and
1077
+ * cannot bill again. Calling it EARLY is also safe — before every entitlement
1078
+ * the composite is restricted to has reported, or while an ADVANCE pool is
1079
+ * full — the server parks the ping and applies it when the blocker clears,
1080
+ * rather than failing it.
1081
+ */
1082
+ async completeComposite(input) {
1083
+ const res = await this.transport.request({
1084
+ method: "POST",
1085
+ path: COMPOSITE_COMPLETE_PATH,
1086
+ body: {
1087
+ customerId: input.customerId,
1088
+ composite: { [input.composite.ref]: input.composite.value },
1089
+ // Auto-assigned like the others. A retry here could not double-BILL
1090
+ // even without it — closing a closed occurrence is a server-side
1091
+ // no-op — but that safety comes from occurrence-level dedup, not from
1092
+ // idempotency, and an un-keyed replay still writes a redundant inbox
1093
+ // row. Keying it collapses the retry into the one attempt it is.
1094
+ idempotencyKey: input.idempotencyKey ?? newKey()
1095
+ },
1096
+ retry: true
1097
+ });
1098
+ return {
1099
+ queued: res.queued === true,
1100
+ ...res.status != null ? { status: res.status } : {},
1101
+ ...res.composite !== void 0 ? { composite: res.composite } : {},
1102
+ ...res.value !== void 0 ? { value: res.value } : {},
1103
+ ...handles(res)
1104
+ };
1105
+ }
1106
+ /**
1107
+ * A customer's unit balances + recent unit events. `GET /api/v1/units`.
1108
+ *
1109
+ * Legacy path for the same reason as {@link list}: the signal family is
1110
+ * POST-only, so the read side has no successor and is not deprecated.
1111
+ */
969
1112
  unitUsage(params) {
970
1113
  return this.transport.request({
971
1114
  method: "GET",
@@ -1109,6 +1252,8 @@ var ClockNext = class {
1109
1252
  payments;
1110
1253
  /** Manage the credit-type catalog. */
1111
1254
  credits;
1255
+ /** Manage the composite catalog — bundles billed as one thing. */
1256
+ composites;
1112
1257
  /** Manage the outcome-type catalog. */
1113
1258
  outcomes;
1114
1259
  /** Manage the unit-type catalog. */
@@ -1137,6 +1282,7 @@ var ClockNext = class {
1137
1282
  this.invoices = new Invoices(transport);
1138
1283
  this.payments = new Payments(transport);
1139
1284
  this.credits = new Credits(transport);
1285
+ this.composites = new Composites(transport);
1140
1286
  this.outcomes = new Outcomes(transport);
1141
1287
  this.units = new Units(transport);
1142
1288
  this.webhooks = new Webhooks(transport);