@clocknext/sdk 0.8.0 → 0.11.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 +80 -18
- package/dist/index.cjs +160 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +386 -71
- package/dist/index.d.ts +386 -71
- package/dist/index.js +160 -44
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -119,7 +119,8 @@ var Flusher = class {
|
|
|
119
119
|
try {
|
|
120
120
|
await this.transport.request({
|
|
121
121
|
method: "POST",
|
|
122
|
-
|
|
122
|
+
// Per-item, not per-Flusher: one route per meter. See `QueuedSignal`.
|
|
123
|
+
path: item.path,
|
|
123
124
|
body: item.body,
|
|
124
125
|
// Best-effort metering opts into retries on transient failures.
|
|
125
126
|
retry: true
|
|
@@ -227,7 +228,7 @@ function sleep(ms) {
|
|
|
227
228
|
}
|
|
228
229
|
|
|
229
230
|
// src/http.ts
|
|
230
|
-
var SDK_VERSION = "0.
|
|
231
|
+
var SDK_VERSION = "0.11.0" ;
|
|
231
232
|
var Transport = class {
|
|
232
233
|
constructor(cfg) {
|
|
233
234
|
this.cfg = cfg;
|
|
@@ -322,6 +323,36 @@ var Transport = class {
|
|
|
322
323
|
}
|
|
323
324
|
};
|
|
324
325
|
|
|
326
|
+
// src/resources/composites.ts
|
|
327
|
+
var Composites = class {
|
|
328
|
+
constructor(transport) {
|
|
329
|
+
this.transport = transport;
|
|
330
|
+
}
|
|
331
|
+
transport;
|
|
332
|
+
/** List composites. Pass `{ active: true }` for only the ones a signal can
|
|
333
|
+
* still be tagged with — the default list INCLUDES archived ones, which
|
|
334
|
+
* keep every row they already own. */
|
|
335
|
+
async list(params = {}) {
|
|
336
|
+
const res = await this.transport.request({
|
|
337
|
+
method: "GET",
|
|
338
|
+
path: "/api/v1/composites",
|
|
339
|
+
query: {
|
|
340
|
+
active: params.active === void 0 ? void 0 : String(params.active)
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
return res.composites;
|
|
344
|
+
}
|
|
345
|
+
/** Create a composite. `POST /api/v1/composites`. */
|
|
346
|
+
async create(input) {
|
|
347
|
+
const res = await this.transport.request({
|
|
348
|
+
method: "POST",
|
|
349
|
+
path: "/api/v1/composites",
|
|
350
|
+
body: input
|
|
351
|
+
});
|
|
352
|
+
return res.composite;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
325
356
|
// src/resources/credits.ts
|
|
326
357
|
var Credits = class {
|
|
327
358
|
constructor(transport) {
|
|
@@ -504,7 +535,7 @@ var Customers = class {
|
|
|
504
535
|
});
|
|
505
536
|
return res.members;
|
|
506
537
|
}
|
|
507
|
-
/** Add a member to a customer. Duplicate `
|
|
538
|
+
/** Add a member to a customer. Duplicate `email` → ConflictError. */
|
|
508
539
|
async addMember(id, input) {
|
|
509
540
|
const res = await this.transport.request({
|
|
510
541
|
method: "POST",
|
|
@@ -513,7 +544,7 @@ var Customers = class {
|
|
|
513
544
|
});
|
|
514
545
|
return res.member;
|
|
515
546
|
}
|
|
516
|
-
/** Update a member's profile
|
|
547
|
+
/** Update a member's profile. Duplicate `email` → ConflictError. `PATCH …/:id/members/:memberId`. */
|
|
517
548
|
async updateMember(id, memberId, input) {
|
|
518
549
|
const res = await this.transport.request({
|
|
519
550
|
method: "PATCH",
|
|
@@ -522,12 +553,13 @@ var Customers = class {
|
|
|
522
553
|
});
|
|
523
554
|
return res.member;
|
|
524
555
|
}
|
|
525
|
-
/** Remove a member from a customer. `DELETE …/:id/members/:memberId`. */
|
|
556
|
+
/** Remove a member from a customer, returning who was removed. `DELETE …/:id/members/:memberId`. */
|
|
526
557
|
async removeMember(id, memberId) {
|
|
527
|
-
await this.transport.request({
|
|
558
|
+
const res = await this.transport.request({
|
|
528
559
|
method: "DELETE",
|
|
529
560
|
path: `/api/v1/customers/${encodeURIComponent(id)}/members/${encodeURIComponent(memberId)}`
|
|
530
561
|
});
|
|
562
|
+
return res.member;
|
|
531
563
|
}
|
|
532
564
|
// --- Wallet & billing config --------------------------------------------
|
|
533
565
|
/** The customer's wallet balance + transactions. `GET …/:id/wallet`. */
|
|
@@ -853,18 +885,28 @@ function newKey() {
|
|
|
853
885
|
}
|
|
854
886
|
|
|
855
887
|
// src/resources/signals.ts
|
|
888
|
+
var SIGNAL_PATHS = {
|
|
889
|
+
credit: "/api/v1/signal/credit",
|
|
890
|
+
outcome: "/api/v1/signal/outcome",
|
|
891
|
+
wallet: "/api/v1/signal/wallet"
|
|
892
|
+
};
|
|
893
|
+
var UNIT_PATH = "/api/v1/signal/unit";
|
|
894
|
+
var COMPOSITE_COMPLETE_PATH = "/api/v1/signal/composite/complete";
|
|
856
895
|
function signalToWire(signal) {
|
|
896
|
+
const t = signal.tokens;
|
|
857
897
|
const body = {
|
|
858
898
|
customerId: signal.customerId,
|
|
859
|
-
|
|
860
|
-
|
|
899
|
+
// `usage` is required on all three model-priced kinds. input/output are
|
|
900
|
+
// always sent; `cacheTokens` only when provided (the server defaults 0).
|
|
901
|
+
usage: {
|
|
902
|
+
model: signal.model,
|
|
903
|
+
inputTokens: t.input,
|
|
904
|
+
outputTokens: t.output,
|
|
905
|
+
...t.cache != null ? { cacheTokens: t.cache } : {}
|
|
906
|
+
}
|
|
861
907
|
};
|
|
862
908
|
if (signal.member) body.member = signal.member;
|
|
863
909
|
if (signal.custom) body.custom = signal.custom;
|
|
864
|
-
const t = signal.tokens;
|
|
865
|
-
body.inputTokens = t.input;
|
|
866
|
-
body.outputTokens = t.output;
|
|
867
|
-
if (t.cache != null) body.cacheTokens = t.cache;
|
|
868
910
|
if (signal.type === "credit" || signal.type === "outcome") {
|
|
869
911
|
body.agentKey = signal.agentKey;
|
|
870
912
|
}
|
|
@@ -872,12 +914,18 @@ function signalToWire(signal) {
|
|
|
872
914
|
body.runId = signal.runId;
|
|
873
915
|
if (signal.complete != null) body.complete = signal.complete;
|
|
874
916
|
}
|
|
875
|
-
if (signal.type !== "wallet" && signal.
|
|
876
|
-
body[signal.
|
|
917
|
+
if (signal.type !== "wallet" && signal.composite) {
|
|
918
|
+
body.composite = { [signal.composite.ref]: signal.composite.value };
|
|
877
919
|
}
|
|
878
920
|
if (signal.idempotencyKey) body.idempotencyKey = signal.idempotencyKey;
|
|
879
921
|
return body;
|
|
880
922
|
}
|
|
923
|
+
function handles(res) {
|
|
924
|
+
return {
|
|
925
|
+
...res.messageId != null ? { messageId: res.messageId } : {},
|
|
926
|
+
...res.rawId != null ? { rawId: res.rawId } : {}
|
|
927
|
+
};
|
|
928
|
+
}
|
|
881
929
|
var Signals = class {
|
|
882
930
|
constructor(transport, flusher, cfg) {
|
|
883
931
|
this.transport = transport;
|
|
@@ -888,7 +936,8 @@ var Signals = class {
|
|
|
888
936
|
flusher;
|
|
889
937
|
cfg;
|
|
890
938
|
/**
|
|
891
|
-
* Record one usage signal
|
|
939
|
+
* Record one usage signal, to the route its `type` selects:
|
|
940
|
+
* `POST /api/v1/signal/credit` | `/signal/outcome` | `/signal/wallet`.
|
|
892
941
|
*
|
|
893
942
|
* Returns as soon as the signal is buffered (async mode) or the request has
|
|
894
943
|
* been answered (`{ wait: true }` / sync mode). See {@link TrackResult}: a
|
|
@@ -897,14 +946,15 @@ var Signals = class {
|
|
|
897
946
|
async track(signal, opts = {}) {
|
|
898
947
|
const body = signalToWire(signal);
|
|
899
948
|
if (body.idempotencyKey == null) body.idempotencyKey = newKey();
|
|
949
|
+
const path = SIGNAL_PATHS[signal.type];
|
|
900
950
|
const sendNow = opts.wait || this.cfg.mode === "sync";
|
|
901
951
|
if (!sendNow) {
|
|
902
|
-
this.flusher.enqueue({ body, signal });
|
|
952
|
+
this.flusher.enqueue({ path, body, signal });
|
|
903
953
|
return { queued: true };
|
|
904
954
|
}
|
|
905
955
|
const res = await this.transport.request({
|
|
906
956
|
method: "POST",
|
|
907
|
-
path
|
|
957
|
+
path,
|
|
908
958
|
body,
|
|
909
959
|
// Best-effort metering opts into retries on transient failures.
|
|
910
960
|
retry: true
|
|
@@ -912,39 +962,30 @@ var Signals = class {
|
|
|
912
962
|
return {
|
|
913
963
|
queued: res.queued === true,
|
|
914
964
|
usageLog: res.usageLog ?? null,
|
|
915
|
-
...res
|
|
965
|
+
...handles(res)
|
|
916
966
|
};
|
|
917
967
|
}
|
|
918
|
-
/**
|
|
919
|
-
* Validate + price a signal WITHOUT recording it (server `dryRun`). Returns
|
|
920
|
-
* the projected usage log so an integration can preflight before sending real
|
|
921
|
-
* traffic — no inbox row, wallet / outcome writes, or webhooks fire.
|
|
922
|
-
*/
|
|
923
|
-
async verify(signal) {
|
|
924
|
-
const body = signalToWire(signal);
|
|
925
|
-
body.dryRun = true;
|
|
926
|
-
const res = await this.transport.request({
|
|
927
|
-
method: "POST",
|
|
928
|
-
path: "/api/v1/usage",
|
|
929
|
-
body,
|
|
930
|
-
// A dry run writes nothing, so it is always safe to retry.
|
|
931
|
-
retry: true
|
|
932
|
-
});
|
|
933
|
-
return res.usageLog ?? null;
|
|
934
|
-
}
|
|
935
|
-
/** Meter a named credit. */
|
|
968
|
+
/** Meter a named credit. `POST /api/v1/signal/credit`. */
|
|
936
969
|
credit(input, opts) {
|
|
937
970
|
return this.track({ ...input, type: "credit" }, opts);
|
|
938
971
|
}
|
|
939
|
-
/** Debit the customer's wallet at model cost. */
|
|
972
|
+
/** Debit the customer's wallet at model cost. `POST /api/v1/signal/wallet`. */
|
|
940
973
|
wallet(input, opts) {
|
|
941
974
|
return this.track({ ...input, type: "wallet" }, opts);
|
|
942
975
|
}
|
|
943
|
-
/** Advance one step of an outcome workflow. */
|
|
976
|
+
/** Advance one step of an outcome workflow. `POST /api/v1/signal/outcome`. */
|
|
944
977
|
outcome(input, opts) {
|
|
945
978
|
return this.track({ ...input, type: "outcome" }, opts);
|
|
946
979
|
}
|
|
947
|
-
/**
|
|
980
|
+
/**
|
|
981
|
+
* Recent usage logs + totals for a customer. `GET /api/v1/usage`.
|
|
982
|
+
*
|
|
983
|
+
* Still the legacy path, deliberately. The `/api/v1/signal/*` family is
|
|
984
|
+
* POST-only — it replaced how usage is INGESTED, not how it is read — so
|
|
985
|
+
* `GET /api/v1/usage` has no successor and is not deprecated. It is also the
|
|
986
|
+
* only way to prove a signal landed, which matters more now that ingestion
|
|
987
|
+
* answers `202` for everything.
|
|
988
|
+
*/
|
|
948
989
|
async list(params) {
|
|
949
990
|
const res = await this.transport.request({
|
|
950
991
|
method: "GET",
|
|
@@ -956,22 +997,94 @@ var Signals = class {
|
|
|
956
997
|
/**
|
|
957
998
|
* Record one unit-metered event (a fixed-price meter with no tokens),
|
|
958
999
|
* addressed to a catalog unit by its `agentKey`. Sent immediately — unit
|
|
959
|
-
* events are not buffered. `POST /api/v1/
|
|
1000
|
+
* events are not buffered. `POST /api/v1/signal/unit`.
|
|
1001
|
+
*
|
|
1002
|
+
* Two things changed with the move off `POST /api/v1/units`, and both show up
|
|
1003
|
+
* in the return type. The route is now durable (the report lands in an inbox
|
|
1004
|
+
* before it is priced, so a failure parks as Pending and can be retried
|
|
1005
|
+
* instead of losing a billable event) and it answers `202` when it queues —
|
|
1006
|
+
* so this resolves {@link UnitTrackResult}, not a bare `{ unitUsage }`.
|
|
1007
|
+
* Branch on `queued`.
|
|
960
1008
|
*/
|
|
961
1009
|
async unit(input) {
|
|
962
|
-
|
|
1010
|
+
const res = await this.transport.request({
|
|
963
1011
|
method: "POST",
|
|
964
|
-
path:
|
|
1012
|
+
path: UNIT_PATH,
|
|
965
1013
|
body: {
|
|
966
1014
|
customerId: input.customerId,
|
|
967
1015
|
agentKey: input.agentKey,
|
|
968
|
-
...input.
|
|
1016
|
+
...input.quantity != null ? { quantity: input.quantity } : {},
|
|
1017
|
+
...input.member ? { member: input.member } : {},
|
|
1018
|
+
...input.composite ? { composite: { [input.composite.ref]: input.composite.value } } : {},
|
|
1019
|
+
// Assigned HERE, before the send, so the SAME key rides every
|
|
1020
|
+
// transport-level retry below. Without it a single 500 followed by a
|
|
1021
|
+
// success is TWO un-keyed reports, and the server dedups on the key —
|
|
1022
|
+
// so it bills the allowance down twice, up to `retry.maxAttempts`
|
|
1023
|
+
// times. The route gained idempotency precisely to stop that; not
|
|
1024
|
+
// using it by default would leave the double-charge reachable from the
|
|
1025
|
+
// client instead of the server.
|
|
1026
|
+
idempotencyKey: input.idempotencyKey ?? newKey()
|
|
969
1027
|
},
|
|
970
1028
|
// Best-effort metering opts into retries on transient failures.
|
|
971
1029
|
retry: true
|
|
972
1030
|
});
|
|
1031
|
+
return {
|
|
1032
|
+
queued: res.queued === true,
|
|
1033
|
+
unitUsage: res.unitUsage ?? null,
|
|
1034
|
+
...handles(res)
|
|
1035
|
+
};
|
|
973
1036
|
}
|
|
974
|
-
/**
|
|
1037
|
+
/**
|
|
1038
|
+
* Close ONE occurrence of a composite — a distinct tag value.
|
|
1039
|
+
* `POST /api/v1/signal/composite/complete`.
|
|
1040
|
+
*
|
|
1041
|
+
* A composite bundles several credits / outcomes / units and is billed as one
|
|
1042
|
+
* thing. The occurrence opens on the first tagged signal and costs nothing;
|
|
1043
|
+
* this call is the moment it is billed. Only your code knows when the
|
|
1044
|
+
* workflow ended, so this is a DECLARATION, not a hint: tagged signals may
|
|
1045
|
+
* arrive in any order and the occurrence stays open and unbilled until you
|
|
1046
|
+
* say otherwise. Put it on the line before your own `return`.
|
|
1047
|
+
*
|
|
1048
|
+
* An OUTCOME never needs this — its own `complete: true` closes its run and
|
|
1049
|
+
* the matching occurrence together. Credits and units have no "done" signal
|
|
1050
|
+
* of their own, so this is theirs.
|
|
1051
|
+
*
|
|
1052
|
+
* Closing twice is safe: the second call answers `ALREADY_COMPLETED` and
|
|
1053
|
+
* cannot bill again. Calling it EARLY is also safe — before every entitlement
|
|
1054
|
+
* the composite is restricted to has reported, or while an ADVANCE pool is
|
|
1055
|
+
* full — the server parks the ping and applies it when the blocker clears,
|
|
1056
|
+
* rather than failing it.
|
|
1057
|
+
*/
|
|
1058
|
+
async completeComposite(input) {
|
|
1059
|
+
const res = await this.transport.request({
|
|
1060
|
+
method: "POST",
|
|
1061
|
+
path: COMPOSITE_COMPLETE_PATH,
|
|
1062
|
+
body: {
|
|
1063
|
+
customerId: input.customerId,
|
|
1064
|
+
composite: { [input.composite.ref]: input.composite.value },
|
|
1065
|
+
// Auto-assigned like the others. A retry here could not double-BILL
|
|
1066
|
+
// even without it — closing a closed occurrence is a server-side
|
|
1067
|
+
// no-op — but that safety comes from occurrence-level dedup, not from
|
|
1068
|
+
// idempotency, and an un-keyed replay still writes a redundant inbox
|
|
1069
|
+
// row. Keying it collapses the retry into the one attempt it is.
|
|
1070
|
+
idempotencyKey: input.idempotencyKey ?? newKey()
|
|
1071
|
+
},
|
|
1072
|
+
retry: true
|
|
1073
|
+
});
|
|
1074
|
+
return {
|
|
1075
|
+
queued: res.queued === true,
|
|
1076
|
+
...res.status != null ? { status: res.status } : {},
|
|
1077
|
+
...res.composite !== void 0 ? { composite: res.composite } : {},
|
|
1078
|
+
...res.value !== void 0 ? { value: res.value } : {},
|
|
1079
|
+
...handles(res)
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* A customer's unit balances + recent unit events. `GET /api/v1/units`.
|
|
1084
|
+
*
|
|
1085
|
+
* Legacy path for the same reason as {@link list}: the signal family is
|
|
1086
|
+
* POST-only, so the read side has no successor and is not deprecated.
|
|
1087
|
+
*/
|
|
975
1088
|
unitUsage(params) {
|
|
976
1089
|
return this.transport.request({
|
|
977
1090
|
method: "GET",
|
|
@@ -1115,6 +1228,8 @@ var ClockNext = class {
|
|
|
1115
1228
|
payments;
|
|
1116
1229
|
/** Manage the credit-type catalog. */
|
|
1117
1230
|
credits;
|
|
1231
|
+
/** Manage the composite catalog — bundles billed as one thing. */
|
|
1232
|
+
composites;
|
|
1118
1233
|
/** Manage the outcome-type catalog. */
|
|
1119
1234
|
outcomes;
|
|
1120
1235
|
/** Manage the unit-type catalog. */
|
|
@@ -1143,6 +1258,7 @@ var ClockNext = class {
|
|
|
1143
1258
|
this.invoices = new Invoices(transport);
|
|
1144
1259
|
this.payments = new Payments(transport);
|
|
1145
1260
|
this.credits = new Credits(transport);
|
|
1261
|
+
this.composites = new Composites(transport);
|
|
1146
1262
|
this.outcomes = new Outcomes(transport);
|
|
1147
1263
|
this.units = new Units(transport);
|
|
1148
1264
|
this.webhooks = new Webhooks(transport);
|