@clocknext/sdk 0.12.0 → 0.13.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 +61 -15
- package/dist/index.cjs +68 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +142 -32
- package/dist/index.d.ts +142 -32
- package/dist/index.js +68 -11
- package/dist/index.js.map +1 -1
- package/examples/node-basic.ts +5 -3
- package/package.json +1 -1
- package/src/config.ts +7 -3
- package/src/index.ts +3 -0
- package/src/resources/customers.ts +26 -3
- package/src/resources/signals.ts +75 -13
- package/src/types.ts +96 -20
package/README.md
CHANGED
|
@@ -46,17 +46,37 @@ A signal is silently discarded if any of these is true:
|
|
|
46
46
|
| a required field is missing | see the tables below |
|
|
47
47
|
| the allowance is exhausted | top up with `customers.adjustCredit()` etc. |
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
Three things follow.
|
|
50
|
+
|
|
51
|
+
**The per-signal verdict lives in the ClockNext dashboard, and only there.**
|
|
52
|
+
Every signal is logged with its outcome — accepted, processed, or failed with a
|
|
53
|
+
reason — on the workspace's signal log. That view is owner-only in the product
|
|
54
|
+
UI by design and is not exposed through this SDK or any API. When usage is not
|
|
55
|
+
being billed and the causes below do not explain it, that log is where you look.
|
|
56
|
+
|
|
57
|
+
**`onError` does not see worker-side rejections.** It fires when the HTTP
|
|
58
|
+
request itself fails — network, timeout, auth, a `4xx` returned *before* the
|
|
59
|
+
`202`. A signal that was accepted and then rejected during pricing never
|
|
60
|
+
reaches it, because from the SDK's point of view that request succeeded. Pass it
|
|
61
|
+
anyway: it is how you catch a bad API key, an unreachable host, and a dropped
|
|
62
|
+
buffer.
|
|
63
|
+
|
|
64
|
+
**`signals.list()` is the programmatic check, and it is partial.** It returns
|
|
65
|
+
priced usage logs, each with a `status`, so it can tell you a signal landed and
|
|
66
|
+
whether pricing succeeded. It cannot tell you about a signal that was rejected
|
|
67
|
+
*before* it was ever priced — an unknown `agentKey`, a customer with no plan —
|
|
68
|
+
because no usage log is written for one. Those appear only in the dashboard.
|
|
56
69
|
|
|
57
70
|
```ts
|
|
58
71
|
const usage = await cnk.signals.list({ customerId });
|
|
59
|
-
|
|
72
|
+
|
|
73
|
+
// Nothing here at all → rejected before pricing. Check the dashboard's log.
|
|
74
|
+
console.log(usage.totals.count);
|
|
75
|
+
|
|
76
|
+
// Present but not billed → priced and refused; `status` says which.
|
|
77
|
+
for (const log of usage.logs) {
|
|
78
|
+
if (log.status !== "SUCCESS") console.warn(log.id, log.status);
|
|
79
|
+
}
|
|
60
80
|
```
|
|
61
81
|
|
|
62
82
|
### 2. Nothing can be metered until five things exist
|
|
@@ -103,7 +123,8 @@ if (!apiKey) throw new Error("CLOCKNEXT_API_KEY is required");
|
|
|
103
123
|
|
|
104
124
|
const cnk = new ClockNext({
|
|
105
125
|
apiKey,
|
|
106
|
-
//
|
|
126
|
+
// Catches request-level failures (bad key, network, dropped buffer).
|
|
127
|
+
// Rejections found later, during pricing, are visible only in the dashboard.
|
|
107
128
|
onError: (error, signal) =>
|
|
108
129
|
console.error("[clocknext] signal rejected", signal?.type, error),
|
|
109
130
|
});
|
|
@@ -213,7 +234,9 @@ than drawing on a named entitlement — and cannot be grouped into a composite.
|
|
|
213
234
|
### Outcome runs
|
|
214
235
|
|
|
215
236
|
An outcome is billed once per run, not once per step. Reuse the same `runId` for
|
|
216
|
-
every step
|
|
237
|
+
every step, then close the run with a separate, dedicated call —
|
|
238
|
+
`signals.completeOutcome()`. `signals.outcome()` accepts ONLY the step shape;
|
|
239
|
+
there is no `complete` flag to set on a step.
|
|
217
240
|
|
|
218
241
|
```ts
|
|
219
242
|
const runId = "ticket_9021";
|
|
@@ -231,13 +254,18 @@ await cnk.signals.outcome({
|
|
|
231
254
|
model: model.modelId,
|
|
232
255
|
agentKey: "resolved_ticket.resolve",
|
|
233
256
|
runId,
|
|
234
|
-
complete: true,
|
|
235
257
|
tokens: { input: 500, output: 200 },
|
|
236
258
|
});
|
|
259
|
+
|
|
260
|
+
// Closing is ALWAYS its own explicit call — never a flag riding along on a
|
|
261
|
+
// step's real usage.
|
|
262
|
+
await cnk.signals.completeOutcome({ customerId, runId });
|
|
237
263
|
```
|
|
238
264
|
|
|
239
265
|
A step may repeat within a run — a verify loop firing three times is three
|
|
240
|
-
signals and one outcome.
|
|
266
|
+
signals and one outcome. Sending `completeOutcome()` twice for the same run is
|
|
267
|
+
a safe no-op; a genuinely SECOND, independent completion for an
|
|
268
|
+
already-completed run is a permanent error (it would bill the run again).
|
|
241
269
|
|
|
242
270
|
### Unit usage
|
|
243
271
|
|
|
@@ -270,7 +298,7 @@ await cnk.signals.credit({
|
|
|
270
298
|
await cnk.signals.completeComposite({ customerId, composite });
|
|
271
299
|
```
|
|
272
300
|
|
|
273
|
-
|
|
301
|
+
**`signals.completeOutcome()` does not close a composite occurrence.** It
|
|
274
302
|
closes the run, and nothing else — whatever mix of credits, outcomes and units
|
|
275
303
|
the composite wraps. If you tag outcome signals with a `composite`, you must
|
|
276
304
|
still call `completeComposite()`, or that occurrence stays open and is never
|
|
@@ -430,7 +458,13 @@ and `.idempotencyKey` on metering calls. Constructor failures — a missing
|
|
|
430
458
|
|
|
431
459
|
Because ingest answers `202` before validating, **a try/catch around
|
|
432
460
|
`signals.credit()` will not catch a rejected signal**, with or without
|
|
433
|
-
`{ wait: true }`.
|
|
461
|
+
`{ wait: true }` — and neither will `onError`. A rejection discovered during
|
|
462
|
+
pricing has no programmatic channel at all; it is recorded on the workspace's
|
|
463
|
+
signal log in the ClockNext dashboard, which is owner-only by design.
|
|
464
|
+
|
|
465
|
+
What the hooks below DO cover is everything that fails before the `202`: an
|
|
466
|
+
invalid API key, an unreachable host, a timeout, and signals the SDK dropped
|
|
467
|
+
without sending.
|
|
434
468
|
|
|
435
469
|
```ts
|
|
436
470
|
import { ClockNext, ClockNextError } from "@clocknext/sdk";
|
|
@@ -452,6 +486,10 @@ const cnk = new ClockNext({
|
|
|
452
486
|
|
|
453
487
|
`onFlush` counts signals the server **accepted**, not signals it billed.
|
|
454
488
|
|
|
489
|
+
So the division of labour is: these hooks tell you your integration cannot
|
|
490
|
+
reach ClockNext; `signals.list()` tells you what got priced; the dashboard's
|
|
491
|
+
signal log tells you why an individual signal did not.
|
|
492
|
+
|
|
455
493
|
## Balances and usage
|
|
456
494
|
|
|
457
495
|
```ts
|
|
@@ -472,12 +510,20 @@ await cnk.signals.list({ customerId, limit: 50 });
|
|
|
472
510
|
await cnk.signals.unitUsage({ customerId, limit: 50 });
|
|
473
511
|
```
|
|
474
512
|
|
|
475
|
-
Adjustments use stable agent keys, not display names
|
|
513
|
+
Adjustments use stable agent keys, not display names. All four return the
|
|
514
|
+
resulting `{ remaining }` balance; a reduce is never rejected or floored for
|
|
515
|
+
going below 0 — the resulting balance may read negative, same as a real-usage
|
|
516
|
+
overdraft already can.
|
|
476
517
|
|
|
477
518
|
```ts
|
|
478
519
|
await cnk.customers.adjustCredit(customerId, { agentKey: "api_credit", delta: 100 });
|
|
479
520
|
await cnk.customers.adjustOutcome(customerId, { agentKey: "resolved_ticket.triage", delta: 1 });
|
|
480
521
|
await cnk.customers.adjustUnit(customerId, { agentKey: "seats", delta: 5 });
|
|
522
|
+
|
|
523
|
+
// Composite: `agentKey` here is actually the composite's `refId` — the same
|
|
524
|
+
// value signals tag it with — named `agentKey` on the wire so all four share
|
|
525
|
+
// one request shape. Adjusts the CURRENT billing cycle's ADVANCE slot pool.
|
|
526
|
+
await cnk.customers.adjustComposite(customerId, { agentKey: "voice_ai", delta: 2 });
|
|
481
527
|
```
|
|
482
528
|
|
|
483
529
|
## Customers, plans, and billing
|
package/dist/index.cjs
CHANGED
|
@@ -328,7 +328,7 @@ function sleep(ms) {
|
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
// src/http.ts
|
|
331
|
-
var SDK_VERSION = "0.
|
|
331
|
+
var SDK_VERSION = "0.13.0" ;
|
|
332
332
|
var Transport = class {
|
|
333
333
|
constructor(cfg) {
|
|
334
334
|
this.cfg = cfg;
|
|
@@ -704,13 +704,15 @@ var Customers = class {
|
|
|
704
704
|
// (PATCH …/:id) — there is no separate currency endpoint. Credit-type
|
|
705
705
|
// assignment has no public API.
|
|
706
706
|
// --- Balance adjustments -------------------------------------------------
|
|
707
|
-
/** Grant or claw back a credit balance.
|
|
707
|
+
/** Grant or claw back a credit balance. Returns the remaining balance.
|
|
708
|
+
* `POST …/:id/credits/adjust`. */
|
|
708
709
|
async adjustCredit(id, input) {
|
|
709
|
-
await this.transport.request({
|
|
710
|
+
const res = await this.transport.request({
|
|
710
711
|
method: "POST",
|
|
711
712
|
path: `/api/v1/customers/${encodeURIComponent(id)}/credits/adjust`,
|
|
712
713
|
body: input
|
|
713
714
|
});
|
|
715
|
+
return { remaining: res.remaining };
|
|
714
716
|
}
|
|
715
717
|
/** Grant or claw back an outcome balance. Returns the remaining balance. */
|
|
716
718
|
async adjustOutcome(id, input) {
|
|
@@ -730,6 +732,19 @@ var Customers = class {
|
|
|
730
732
|
});
|
|
731
733
|
return { remaining: res.remaining };
|
|
732
734
|
}
|
|
735
|
+
/**
|
|
736
|
+
* Grant or claw back slots on a customer's ADVANCE composite pool for the
|
|
737
|
+
* current billing cycle. Returns the resulting `remaining` slot count.
|
|
738
|
+
* `POST …/:id/composites/adjust`.
|
|
739
|
+
*/
|
|
740
|
+
async adjustComposite(id, input) {
|
|
741
|
+
const res = await this.transport.request({
|
|
742
|
+
method: "POST",
|
|
743
|
+
path: `/api/v1/customers/${encodeURIComponent(id)}/composites/adjust`,
|
|
744
|
+
body: input
|
|
745
|
+
});
|
|
746
|
+
return { remaining: res.remaining };
|
|
747
|
+
}
|
|
733
748
|
};
|
|
734
749
|
|
|
735
750
|
// src/resources/invoices.ts
|
|
@@ -1015,6 +1030,7 @@ var SIGNAL_PATHS = {
|
|
|
1015
1030
|
};
|
|
1016
1031
|
var UNIT_PATH = "/api/v1/signal/unit";
|
|
1017
1032
|
var COMPOSITE_COMPLETE_PATH = "/api/v1/signal/composite/complete";
|
|
1033
|
+
var OUTCOME_COMPLETE_PATH = "/api/v1/signal/outcome/complete";
|
|
1018
1034
|
function signalToWire(signal) {
|
|
1019
1035
|
const t = signal.tokens;
|
|
1020
1036
|
const body = {
|
|
@@ -1035,7 +1051,6 @@ function signalToWire(signal) {
|
|
|
1035
1051
|
}
|
|
1036
1052
|
if (signal.type === "outcome") {
|
|
1037
1053
|
body.runId = signal.runId;
|
|
1038
|
-
if (signal.complete != null) body.complete = signal.complete;
|
|
1039
1054
|
}
|
|
1040
1055
|
if (signal.type !== "wallet" && signal.composite) {
|
|
1041
1056
|
body.composite = { [signal.composite.ref]: signal.composite.value };
|
|
@@ -1105,6 +1120,48 @@ var Signals = class {
|
|
|
1105
1120
|
outcome(input, opts) {
|
|
1106
1121
|
return this.track({ ...input, type: "outcome" }, opts);
|
|
1107
1122
|
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Close ONE outcome run — declares the workflow finished and bills it. The
|
|
1125
|
+
* outcome mirror of {@link Signals.completeComposite}, on its own dedicated
|
|
1126
|
+
* route: `POST /api/v1/signal/outcome/complete`.
|
|
1127
|
+
*
|
|
1128
|
+
* An outcome bills per COMPLETION, never per step, and `signals.outcome()`
|
|
1129
|
+
* never closes one — it accepts only the step shape. Declaring the workflow
|
|
1130
|
+
* finished is always this separate, explicit, contentless call.
|
|
1131
|
+
*
|
|
1132
|
+
* This closes the RUN only. If a step of this run also carried a
|
|
1133
|
+
* `composite`, that occurrence stays open and unbilled until you separately
|
|
1134
|
+
* call {@link Signals.completeComposite} — the two completions are
|
|
1135
|
+
* independent, and neither implies the other.
|
|
1136
|
+
*
|
|
1137
|
+
* Sent immediately, never buffered. A genuinely second, independent call for
|
|
1138
|
+
* an already-closed run is a PERMANENT error (it would bill the run twice),
|
|
1139
|
+
* not a safe no-op — see {@link OutcomeCompletionResult}.
|
|
1140
|
+
*/
|
|
1141
|
+
async completeOutcome(input) {
|
|
1142
|
+
const idempotencyKey = input.idempotencyKey ?? newKey();
|
|
1143
|
+
const res = await this.transport.request({
|
|
1144
|
+
method: "POST",
|
|
1145
|
+
path: OUTCOME_COMPLETE_PATH,
|
|
1146
|
+
idempotencyKey,
|
|
1147
|
+
body: {
|
|
1148
|
+
customerId: input.customerId,
|
|
1149
|
+
runId: input.runId,
|
|
1150
|
+
complete: true,
|
|
1151
|
+
...input.member ? { member: input.member } : {},
|
|
1152
|
+
idempotencyKey
|
|
1153
|
+
},
|
|
1154
|
+
retry: true
|
|
1155
|
+
});
|
|
1156
|
+
const queued = res.queued === true;
|
|
1157
|
+
return {
|
|
1158
|
+
delivery: queued ? "accepted" : "recorded",
|
|
1159
|
+
queued,
|
|
1160
|
+
...res.status != null ? { status: res.status } : {},
|
|
1161
|
+
usageLog: res.usageLog ?? null,
|
|
1162
|
+
...handles(res)
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1108
1165
|
/**
|
|
1109
1166
|
* Recent usage logs + totals for a customer. `GET /api/v1/usage`.
|
|
1110
1167
|
*
|
|
@@ -1177,13 +1234,13 @@ var Signals = class {
|
|
|
1177
1234
|
* arrive in any order and the occurrence stays open and unbilled until you
|
|
1178
1235
|
* say otherwise. Put it on the line before your own `return`.
|
|
1179
1236
|
*
|
|
1180
|
-
* EVERY composite needs this, outcome-backed ones included.
|
|
1181
|
-
*
|
|
1182
|
-
*
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
1185
|
-
*
|
|
1186
|
-
*
|
|
1237
|
+
* EVERY composite needs this, outcome-backed ones included. `completeOutcome()`
|
|
1238
|
+
* closes its RUN and nothing else — it does not close the composite
|
|
1239
|
+
* occurrence the run was tagged into, whatever mix of credits, outcomes and
|
|
1240
|
+
* units that composite wraps. (Earlier versions of this SDK documented the
|
|
1241
|
+
* opposite. Believing it means the occurrence stays ONGOING forever and is
|
|
1242
|
+
* never billed, so if you tag outcome signals with a `composite`, you must
|
|
1243
|
+
* still call this.)
|
|
1187
1244
|
*
|
|
1188
1245
|
* Closing twice is safe: the second call answers `ALREADY_COMPLETED` and
|
|
1189
1246
|
* cannot bill again. Calling it EARLY is also safe — before every entitlement
|