@clocknext/sdk 0.14.0 → 0.16.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/dist/index.cjs CHANGED
@@ -65,6 +65,7 @@ var FIELD_PATHS = [
65
65
  [/\busage\.inputTokens\b/g, "tokens.input"],
66
66
  [/\busage\.outputTokens\b/g, "tokens.output"],
67
67
  [/\busage\.cacheTokens\b/g, "tokens.cache"],
68
+ [/\busage\.cacheWriteTokens\b/g, "tokens.cacheWrite"],
68
69
  [/\busage\.model\b/g, "model"],
69
70
  // A missing/invalid `usage` block as a whole: the caller's equivalent is
70
71
  // `tokens` (plus `model`, which the SDK hoists to the top level).
@@ -77,7 +78,8 @@ function translateFieldPaths(message) {
77
78
  }
78
79
  return out;
79
80
  }
80
- var ALLOWANCE_MESSAGE = /\binsufficient\b|\bno allowance left\b|\bno slots left\b|\boutcomes remaining\b|\btop up the (?:allowance|balance)\b/i;
81
+ var ALLOWANCE_MESSAGE = /\binsufficient\b|\bno allowance left\b|\bno slots left\b|\boutcomes remaining\b|\btop up the (?:allowance|balance)\b|\bbelow zero\b/i;
82
+ var IDEMPOTENCY_KEY_REUSED = /\bidempotency key was already used\b/i;
81
83
  function isAllowanceFailure(message, code) {
82
84
  if (code) return code === "ALLOWANCE_EXHAUSTED";
83
85
  return ALLOWANCE_MESSAGE.test(message);
@@ -101,6 +103,9 @@ function errorFromResponse(status, message, retryAfterMs, meta = {}) {
101
103
  });
102
104
  }
103
105
  if (status === 422) {
106
+ if (IDEMPOTENCY_KEY_REUSED.test(raw)) {
107
+ return new ConflictError(raw, { ...opts, retryable: false });
108
+ }
104
109
  return isAllowanceFailure(raw, code) ? new AllowanceError(raw, opts) : new PlanError(raw, opts);
105
110
  }
106
111
  if (status === 429) return new RateLimitError(raw, { retryAfterMs, idempotencyKey });
@@ -328,7 +333,7 @@ function sleep(ms) {
328
333
  }
329
334
 
330
335
  // src/http.ts
331
- var SDK_VERSION = "0.14.0" ;
336
+ var SDK_VERSION = "0.16.0" ;
332
337
  var Transport = class {
333
338
  constructor(cfg) {
334
339
  this.cfg = cfg;
@@ -446,11 +451,8 @@ var Composites = class {
446
451
  this.transport = transport;
447
452
  }
448
453
  transport;
449
- /** List composites. Pass `{ active: true }` for only the ones a signal can
450
- * still be tagged with — the default list INCLUDES archived ones, which
451
- * keep every row they already own.
452
- * `{ active: false }` returns only archived ones; omitting it returns
453
- * both. See `activeQuery` — the API itself only supports `true`. */
454
+ /** List them. `{ active: true }` returns only active ones, `{ active: false }`
455
+ * only archived ones; omit it for both. */
454
456
  async list(params = {}) {
455
457
  const res = await this.transport.request({
456
458
  method: "GET",
@@ -459,7 +461,16 @@ var Composites = class {
459
461
  });
460
462
  return filterByActive(res.composites, params.active);
461
463
  }
462
- /** Create a composite. `POST /api/v1/composites`. */
464
+ /** One composite's configuration. */
465
+ async get(id) {
466
+ const res = await this.transport.request({
467
+ method: "GET",
468
+ path: `/api/v1/composites/${encodeURIComponent(id)}`
469
+ });
470
+ return res.composite;
471
+ }
472
+ /** Create one. It starts active. Not retried on failure — a retry could
473
+ * create a second row. */
463
474
  async create(input) {
464
475
  const res = await this.transport.request({
465
476
  method: "POST",
@@ -468,6 +479,35 @@ var Composites = class {
468
479
  });
469
480
  return res.composite;
470
481
  }
482
+ /** PARTIAL update: only the fields you send change. `entitlements`, when
483
+ * sent, is the complete new set. Renaming `refId` cuts live traffic over at
484
+ * once — ship the new tag to your callers first. */
485
+ async update(id, input) {
486
+ const res = await this.transport.request({
487
+ method: "PATCH",
488
+ path: `/api/v1/composites/${encodeURIComponent(id)}`,
489
+ body: input
490
+ });
491
+ return res.composite;
492
+ }
493
+ /** Retire it (`isActive` → false). Nothing is deleted. Safe to retry. */
494
+ async archive(id) {
495
+ const res = await this.transport.request({
496
+ method: "POST",
497
+ path: `/api/v1/composites/${encodeURIComponent(id)}/archive`,
498
+ retry: true
499
+ });
500
+ return res.composite;
501
+ }
502
+ /** Bring an archived one back (`isActive` → true). Safe to retry. */
503
+ async unarchive(id) {
504
+ const res = await this.transport.request({
505
+ method: "POST",
506
+ path: `/api/v1/composites/${encodeURIComponent(id)}/unarchive`,
507
+ retry: true
508
+ });
509
+ return res.composite;
510
+ }
471
511
  };
472
512
 
473
513
  // src/resources/credits.ts
@@ -476,9 +516,8 @@ var Credits = class {
476
516
  this.transport = transport;
477
517
  }
478
518
  transport;
479
- /** List credit types. Pass `{ active: true }` to only return active ones.
480
- * `{ active: false }` returns only archived ones; omitting it returns
481
- * both. See `activeQuery` — the API itself only supports `true`. */
519
+ /** List them. `{ active: true }` returns only active ones, `{ active: false }`
520
+ * only archived ones; omit it for both. */
482
521
  async list(params = {}) {
483
522
  const res = await this.transport.request({
484
523
  method: "GET",
@@ -487,24 +526,26 @@ var Credits = class {
487
526
  });
488
527
  return filterByActive(res.credits, params.active);
489
528
  }
490
- /** Create a credit type; returns the created credit. `POST /api/v1/credits`. */
491
- async create(input) {
529
+ /** One credit's configuration. */
530
+ async get(id) {
492
531
  const res = await this.transport.request({
493
- method: "POST",
494
- path: "/api/v1/credits",
495
- body: input
532
+ method: "GET",
533
+ path: `/api/v1/credits/${encodeURIComponent(id)}`
496
534
  });
497
535
  return res.credit;
498
536
  }
499
- /** Fetch one credit type with usage stats. `GET /api/v1/credits/:id`. */
500
- async get(id) {
537
+ /** Create one. It starts active. Not retried on failure — a retry could
538
+ * create a second row. */
539
+ async create(input) {
501
540
  const res = await this.transport.request({
502
- method: "GET",
503
- path: `/api/v1/credits/${encodeURIComponent(id)}`
541
+ method: "POST",
542
+ path: "/api/v1/credits",
543
+ body: input
504
544
  });
505
545
  return res.credit;
506
546
  }
507
- /** Update a credit type's definition; returns the updated credit. `PATCH /api/v1/credits/:id`. */
547
+ /** PARTIAL update: only the fields you send change. Send `modelBundle` to
548
+ * re-price; leave it out to keep the stored base price. */
508
549
  async update(id, input) {
509
550
  const res = await this.transport.request({
510
551
  method: "PATCH",
@@ -513,24 +554,37 @@ var Credits = class {
513
554
  });
514
555
  return res.credit;
515
556
  }
516
- /** Activate or deactivate a credit type. */
517
- async setActive(id, isActive) {
557
+ /** Retire it (`isActive` → false). Nothing is deleted. Safe to retry. */
558
+ async archive(id) {
518
559
  const res = await this.transport.request({
519
- method: "PATCH",
520
- path: `/api/v1/credits/${encodeURIComponent(id)}`,
521
- body: { isActive }
560
+ method: "POST",
561
+ path: `/api/v1/credits/${encodeURIComponent(id)}/archive`,
562
+ retry: true
522
563
  });
523
564
  return res.credit;
524
565
  }
525
- /** Delete a credit type. Refused while a customer is linked to it. */
526
- async delete(id) {
527
- await this.transport.request({
528
- method: "DELETE",
529
- path: `/api/v1/credits/${encodeURIComponent(id)}`
566
+ /** Bring an archived one back (`isActive` → true). Safe to retry. */
567
+ async unarchive(id) {
568
+ const res = await this.transport.request({
569
+ method: "POST",
570
+ path: `/api/v1/credits/${encodeURIComponent(id)}/unarchive`,
571
+ retry: true
530
572
  });
573
+ return res.credit;
531
574
  }
532
575
  };
533
576
 
577
+ // src/idempotency.ts
578
+ function newKey() {
579
+ const c = globalThis.crypto;
580
+ if (typeof c?.randomUUID === "function") return c.randomUUID();
581
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
582
+ const r = Math.random() * 16 | 0;
583
+ const v = ch === "x" ? r : r & 3 | 8;
584
+ return v.toString(16);
585
+ });
586
+ }
587
+
534
588
  // src/resources/customers.ts
535
589
  var Customers = class {
536
590
  constructor(transport) {
@@ -584,6 +638,36 @@ var Customers = class {
584
638
  });
585
639
  return res.customer;
586
640
  }
641
+ /**
642
+ * Fetch one customer by YOUR id for them (its `externalId`), instead of
643
+ * ours. Same result as {@link get}. `GET /api/v1/customers/ext/:externalId`.
644
+ * An unknown external id is a `NotFoundError`; only active customers match.
645
+ */
646
+ async getByExternalId(externalId) {
647
+ const res = await this.transport.request({
648
+ method: "GET",
649
+ path: `/api/v1/customers/ext/${encodeURIComponent(externalId)}`
650
+ });
651
+ return res.customer;
652
+ }
653
+ /** Update a customer (partial), addressed by its `externalId`. Same body as
654
+ * {@link update}. `PATCH /api/v1/customers/ext/:externalId`. */
655
+ async updateByExternalId(externalId, input) {
656
+ const res = await this.transport.request({
657
+ method: "PATCH",
658
+ path: `/api/v1/customers/ext/${encodeURIComponent(externalId)}`,
659
+ body: input
660
+ });
661
+ return res.customer;
662
+ }
663
+ /** {@link delete}, addressing the customer by its `externalId`.
664
+ * `DELETE /api/v1/customers/ext/:externalId`. */
665
+ async deleteByExternalId(externalId) {
666
+ await this.transport.request({
667
+ method: "DELETE",
668
+ path: `/api/v1/customers/ext/${encodeURIComponent(externalId)}`
669
+ });
670
+ }
587
671
  /** Delete a customer (members, invitations, usage logs cascade). */
588
672
  async delete(id) {
589
673
  await this.transport.request({
@@ -591,15 +675,6 @@ var Customers = class {
591
675
  path: `/api/v1/customers/${encodeURIComponent(id)}`
592
676
  });
593
677
  }
594
- /** A customer's usage history. `GET /api/v1/usage?customerId=`. */
595
- async usage(id, params = {}) {
596
- const res = await this.transport.request({
597
- method: "GET",
598
- path: `/api/v1/usage`,
599
- query: { customerId: id, limit: params.limit }
600
- });
601
- return { customer: res.customer, totals: res.totals, logs: res.logs };
602
- }
603
678
  /** Wallet / credit / outcome / unit balances. `GET …/:id/balances`. */
604
679
  balances(id) {
605
680
  return this.transport.request({
@@ -607,6 +682,14 @@ var Customers = class {
607
682
  path: `/api/v1/customers/${encodeURIComponent(id)}/balances`
608
683
  });
609
684
  }
685
+ /** {@link balances}, addressing the customer by its `externalId`.
686
+ * `GET /api/v1/customers/ext/:externalId/balances`. */
687
+ balancesByExternalId(externalId) {
688
+ return this.transport.request({
689
+ method: "GET",
690
+ path: `/api/v1/customers/ext/${encodeURIComponent(externalId)}/balances`
691
+ });
692
+ }
610
693
  /**
611
694
  * One credit balance for a customer, matched by `creditId` or `creditName`;
612
695
  * `null` when the customer has no plan or no matching credit. Convenience
@@ -645,6 +728,14 @@ var Customers = class {
645
728
  path: `/api/v1/customers/${encodeURIComponent(id)}/plan`
646
729
  });
647
730
  }
731
+ /** {@link plan}, addressing the customer by its `externalId`.
732
+ * `GET /api/v1/customers/ext/:externalId/plan`. */
733
+ planByExternalId(externalId) {
734
+ return this.transport.request({
735
+ method: "GET",
736
+ path: `/api/v1/customers/ext/${encodeURIComponent(externalId)}/plan`
737
+ });
738
+ }
648
739
  // --- Members -------------------------------------------------------------
649
740
  /** List a customer's members. `GET …/:id/members`. */
650
741
  async listMembers(id) {
@@ -680,72 +771,129 @@ var Customers = class {
680
771
  });
681
772
  return res.member;
682
773
  }
683
- // --- Wallet & billing config --------------------------------------------
684
- /** The customer's wallet balance + transactions. `GET …/:id/wallet`. */
685
- wallet(id, params = {}) {
686
- return this.transport.request({
687
- method: "GET",
688
- path: `/api/v1/customers/${encodeURIComponent(id)}/wallet`,
689
- query: { limit: params.limit }
690
- });
691
- }
692
- /**
693
- * Record a manual wallet entry (top-up or deduction). `amount` is always
694
- * positive; the sign comes from `type`. `POST …/:id/wallet`.
695
- */
696
- async addWalletEntry(id, input) {
697
- return this.transport.request({
698
- method: "POST",
699
- path: `/api/v1/customers/${encodeURIComponent(id)}/wallet`,
700
- body: input
701
- });
702
- }
774
+ // --- Billing config -----------------------------------------------------
703
775
  // Note: a customer's currency is set through `update(id, { currencyCode })`
704
776
  // (PATCH …/:id) — there is no separate currency endpoint. Credit-type
705
777
  // assignment has no public API.
706
778
  // --- Balance adjustments -------------------------------------------------
707
- /** Grant or claw back a credit balance. Returns the remaining balance.
708
- * `POST …/:id/credits/adjust`. */
709
- async adjustCredit(id, input) {
710
- const res = await this.transport.request({
711
- method: "POST",
712
- path: `/api/v1/customers/${encodeURIComponent(id)}/credits/adjust`,
713
- body: input
714
- });
715
- return { remaining: res.remaining };
779
+ /** Grant or claw back a credit balance. The credit must be a prepaid part of
780
+ * the customer's current plan. Pass `idempotencyKey` to make it safe to
781
+ * retry. `POST …/:id/credits/adjust`. */
782
+ adjustCredit(id, input) {
783
+ return this.adjust(`/api/v1/customers/${encodeURIComponent(id)}/credits/adjust`, input);
784
+ }
785
+ /** Grant or claw back an outcome balance. The outcome must be a prepaid part
786
+ * of the customer's current plan. `POST …/:id/outcomes/adjust`. */
787
+ adjustOutcome(id, input) {
788
+ return this.adjust(`/api/v1/customers/${encodeURIComponent(id)}/outcomes/adjust`, input);
789
+ }
790
+ /** Grant or claw back a unit balance. The unit must be a prepaid part of the
791
+ * customer's current plan. `POST …/:id/units/adjust`. */
792
+ adjustUnit(id, input) {
793
+ return this.adjust(`/api/v1/customers/${encodeURIComponent(id)}/units/adjust`, input);
794
+ }
795
+ /** Grant or claw back slots on a customer's ADVANCE composite pool for the
796
+ * current billing cycle. `remaining` is the resulting slot count.
797
+ * `POST …/:id/composites/adjust`. */
798
+ adjustComposite(id, input) {
799
+ return this.adjust(`/api/v1/customers/${encodeURIComponent(id)}/composites/adjust`, input);
800
+ }
801
+ /**
802
+ * Top up (positive `delta`) or draw down (negative `delta`) a customer's
803
+ * wallet, in USD — allowed whatever the customer's plan. `remaining` is the
804
+ * resulting USD balance. No `agentKey` — a customer has exactly one wallet.
805
+ * Pass `pool: "reserved"` to move the Reserved Wallet instead (then
806
+ * `remaining` is its balance). `POST …/:id/wallet/adjust`.
807
+ */
808
+ adjustWallet(id, input) {
809
+ return this.adjust(`/api/v1/customers/${encodeURIComponent(id)}/wallet/adjust`, input);
810
+ }
811
+ /** {@link adjustCredit}, addressing the customer by its `externalId` (`id`
812
+ * here). `POST /api/v1/customers/ext/:externalId/credits/adjust`. */
813
+ adjustCreditByExternalId(id, input) {
814
+ return this.adjust(`/api/v1/customers/ext/${encodeURIComponent(id)}/credits/adjust`, input);
815
+ }
816
+ /** {@link adjustOutcome}, addressing the customer by its `externalId` (`id`
817
+ * here). `POST /api/v1/customers/ext/:externalId/outcomes/adjust`. */
818
+ adjustOutcomeByExternalId(id, input) {
819
+ return this.adjust(`/api/v1/customers/ext/${encodeURIComponent(id)}/outcomes/adjust`, input);
820
+ }
821
+ /** {@link adjustUnit}, addressing the customer by its `externalId` (`id`
822
+ * here). `POST /api/v1/customers/ext/:externalId/units/adjust`. */
823
+ adjustUnitByExternalId(id, input) {
824
+ return this.adjust(`/api/v1/customers/ext/${encodeURIComponent(id)}/units/adjust`, input);
825
+ }
826
+ /** {@link adjustComposite}, addressing the customer by its `externalId`
827
+ * (`id` here). `POST /api/v1/customers/ext/:externalId/composites/adjust`. */
828
+ adjustCompositeByExternalId(id, input) {
829
+ return this.adjust(`/api/v1/customers/ext/${encodeURIComponent(id)}/composites/adjust`, input);
830
+ }
831
+ /** {@link adjustWallet}, addressing the customer by its `externalId` (`id`
832
+ * here). `POST /api/v1/customers/ext/:externalId/wallet/adjust`. */
833
+ adjustWalletByExternalId(id, input) {
834
+ return this.adjust(`/api/v1/customers/ext/${encodeURIComponent(id)}/wallet/adjust`, input);
716
835
  }
717
- /** Grant or claw back an outcome balance. Returns the remaining balance. */
718
- async adjustOutcome(id, input) {
836
+ /**
837
+ * The one body behind all ten adjust calls.
838
+ *
839
+ * Every adjustment carries an idempotency key — the caller's, or one
840
+ * generated here ONCE per call, exactly like signals. The server
841
+ * deduplicates on it, so a transient failure is safe to retry: the same key
842
+ * rides every attempt, and a retry of one that already landed comes back as
843
+ * `replayed: true` instead of adjusting again. The key is also copied onto
844
+ * any thrown error (`error.idempotencyKey`) so the caller can replay safely.
845
+ */
846
+ async adjust(path, input) {
847
+ const idempotencyKey = input.idempotencyKey ?? newKey();
719
848
  const res = await this.transport.request({
720
849
  method: "POST",
721
- path: `/api/v1/customers/${encodeURIComponent(id)}/outcomes/adjust`,
722
- body: input
850
+ path,
851
+ idempotencyKey,
852
+ body: { ...input, idempotencyKey },
853
+ retry: true
723
854
  });
724
- return { remaining: res.remaining };
855
+ const result = {
856
+ remaining: res.remaining,
857
+ replayed: res.replayed === true,
858
+ customerArchived: res.customerArchived === true
859
+ };
860
+ if (result.replayed && typeof res.adjustedAt === "string") {
861
+ result.adjustedAt = res.adjustedAt;
862
+ }
863
+ return result;
725
864
  }
726
- /** Grant or claw back a unit balance. Returns the remaining balance. */
727
- async adjustUnit(id, input) {
865
+ // --- Recurring grants ----------------------------------------------------
866
+ /**
867
+ * Change how much a customer is granted every billing cycle (wallet top-up,
868
+ * credits, outcomes, units or a composite pool). Any increase or decrease
869
+ * applies to their current balance immediately. `PUT …/:id/recurring-grant`.
870
+ */
871
+ async updateRecurringGrant(id, input) {
728
872
  const res = await this.transport.request({
729
- method: "POST",
730
- path: `/api/v1/customers/${encodeURIComponent(id)}/units/adjust`,
873
+ method: "PUT",
874
+ path: `/api/v1/customers/${encodeURIComponent(id)}/recurring-grant`,
731
875
  body: input
732
876
  });
733
- return { remaining: res.remaining };
877
+ return toRecurringGrantResult(res);
734
878
  }
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) {
879
+ /** {@link updateRecurringGrant}, addressing the customer by its
880
+ * `externalId`. `PUT /api/v1/customers/ext/:externalId/recurring-grant`. */
881
+ async updateRecurringGrantByExternalId(externalId, input) {
741
882
  const res = await this.transport.request({
742
- method: "POST",
743
- path: `/api/v1/customers/${encodeURIComponent(id)}/composites/adjust`,
883
+ method: "PUT",
884
+ path: `/api/v1/customers/ext/${encodeURIComponent(externalId)}/recurring-grant`,
744
885
  body: input
745
886
  });
746
- return { remaining: res.remaining };
887
+ return toRecurringGrantResult(res);
747
888
  }
748
889
  };
890
+ function toRecurringGrantResult(res) {
891
+ return {
892
+ kind: res.kind,
893
+ quantity: res.quantity,
894
+ ...res.balanceUsd !== void 0 ? { balanceUsd: res.balanceUsd } : {}
895
+ };
896
+ }
749
897
 
750
898
  // src/resources/invoices.ts
751
899
  var Invoices = class {
@@ -774,7 +922,7 @@ var Invoices = class {
774
922
  });
775
923
  }
776
924
  /**
777
- * Mint a hosted pay link for an OPEN invoice. PAID/VOID invoices are rejected
925
+ * Mint a hosted pay link for an OPEN invoice. PAID/VOID/AUTO_CLOSED invoices are rejected
778
926
  * with a `ConflictError`. `GET /api/v1/invoices/:id/pay-link`.
779
927
  */
780
928
  payLink(id) {
@@ -791,9 +939,8 @@ var Outcomes = class {
791
939
  this.transport = transport;
792
940
  }
793
941
  transport;
794
- /** List outcome types. Pass `{ active: true }` to only return active ones.
795
- * `{ active: false }` returns only archived ones; omitting it returns
796
- * both. See `activeQuery` — the API itself only supports `true`. */
942
+ /** List them. `{ active: true }` returns only active ones, `{ active: false }`
943
+ * only archived ones; omit it for both. */
797
944
  async list(params = {}) {
798
945
  const res = await this.transport.request({
799
946
  method: "GET",
@@ -802,24 +949,27 @@ var Outcomes = class {
802
949
  });
803
950
  return filterByActive(res.outcomes, params.active);
804
951
  }
805
- /** Create an outcome type with its steps. `POST /api/v1/outcomes`. */
806
- async create(input) {
952
+ /** One outcome's configuration, with every step in full. */
953
+ async get(id) {
807
954
  const res = await this.transport.request({
808
- method: "POST",
809
- path: "/api/v1/outcomes",
810
- body: input
955
+ method: "GET",
956
+ path: `/api/v1/outcomes/${encodeURIComponent(id)}`
811
957
  });
812
958
  return res.outcome;
813
959
  }
814
- /** Fetch one outcome type with steps + stats. `GET /api/v1/outcomes/:id`. */
815
- async get(id) {
960
+ /** Create one. It starts active. Not retried on failure — a retry could
961
+ * create a second row. */
962
+ async create(input) {
816
963
  const res = await this.transport.request({
817
- method: "GET",
818
- path: `/api/v1/outcomes/${encodeURIComponent(id)}`
964
+ method: "POST",
965
+ path: "/api/v1/outcomes",
966
+ body: input
819
967
  });
820
968
  return res.outcome;
821
969
  }
822
- /** Update an outcome type's definition. `PATCH /api/v1/outcomes/:id`. */
970
+ /** PARTIAL update: only the fields you send change. Leave `steps` out to
971
+ * leave the steps alone; send it and it is the complete list — pass each
972
+ * existing step's `id` (or its same `agentKey`) to keep it. */
823
973
  async update(id, input) {
824
974
  const res = await this.transport.request({
825
975
  method: "PATCH",
@@ -828,21 +978,23 @@ var Outcomes = class {
828
978
  });
829
979
  return res.outcome;
830
980
  }
831
- /** Activate or deactivate an outcome type; returns the updated outcome. */
832
- async setActive(id, isActive) {
981
+ /** Retire it (`isActive` → false). Nothing is deleted. Safe to retry. */
982
+ async archive(id) {
833
983
  const res = await this.transport.request({
834
- method: "PATCH",
835
- path: `/api/v1/outcomes/${encodeURIComponent(id)}`,
836
- body: { isActive }
984
+ method: "POST",
985
+ path: `/api/v1/outcomes/${encodeURIComponent(id)}/archive`,
986
+ retry: true
837
987
  });
838
988
  return res.outcome;
839
989
  }
840
- /** Delete an outcome type. */
841
- async delete(id) {
842
- await this.transport.request({
843
- method: "DELETE",
844
- path: `/api/v1/outcomes/${encodeURIComponent(id)}`
990
+ /** Bring an archived one back (`isActive` → true). Safe to retry. */
991
+ async unarchive(id) {
992
+ const res = await this.transport.request({
993
+ method: "POST",
994
+ path: `/api/v1/outcomes/${encodeURIComponent(id)}/unarchive`,
995
+ retry: true
845
996
  });
997
+ return res.outcome;
846
998
  }
847
999
  };
848
1000
 
@@ -887,24 +1039,27 @@ var Plans = class {
887
1039
  });
888
1040
  return filterByActive(res.plans, params.active);
889
1041
  }
890
- /** Create a plan. `POST /api/v1/plans`. */
891
- async create(input) {
1042
+ /** One plan's configuration. */
1043
+ async get(id) {
892
1044
  const res = await this.transport.request({
893
- method: "POST",
894
- path: "/api/v1/plans",
895
- body: input
1045
+ method: "GET",
1046
+ path: `/api/v1/plans/${encodeURIComponent(id)}`
896
1047
  });
897
1048
  return res.plan;
898
1049
  }
899
- /** Fetch one plan with aggregate subscriber/invoice stats. `GET /api/v1/plans/:id`. */
900
- async get(id) {
1050
+ /** Create a plan. It starts active. Not retried on failure — a retry could
1051
+ * create a second plan. */
1052
+ async create(input) {
901
1053
  const res = await this.transport.request({
902
- method: "GET",
903
- path: `/api/v1/plans/${encodeURIComponent(id)}`
1054
+ method: "POST",
1055
+ path: "/api/v1/plans",
1056
+ body: input
904
1057
  });
905
1058
  return res.plan;
906
1059
  }
907
- /** Replace a plan's definition (components + settings). `PATCH /api/v1/plans/:id`. */
1060
+ /** PARTIAL update: only the fields you send change. `entitlements`, when
1061
+ * sent, is the complete new list — the list `get()` returned can be sent
1062
+ * back as it is. Changes apply to new purchases only. */
908
1063
  async update(id, input) {
909
1064
  const res = await this.transport.request({
910
1065
  method: "PATCH",
@@ -913,21 +1068,24 @@ var Plans = class {
913
1068
  });
914
1069
  return res.plan;
915
1070
  }
916
- /** Activate or deactivate a plan without rewriting it. */
917
- async setActive(id, isActive) {
1071
+ /** Retire a plan (`isActive` → false): it can't be bought any more; customers
1072
+ * already on it are unaffected. Safe to retry. */
1073
+ async archive(id) {
918
1074
  const res = await this.transport.request({
919
- method: "PATCH",
920
- path: `/api/v1/plans/${encodeURIComponent(id)}`,
921
- body: { isActive }
1075
+ method: "POST",
1076
+ path: `/api/v1/plans/${encodeURIComponent(id)}/archive`,
1077
+ retry: true
922
1078
  });
923
1079
  return res.plan;
924
1080
  }
925
- /** Delete a plan. Refused once any customer has purchased it. */
926
- async delete(id) {
927
- await this.transport.request({
928
- method: "DELETE",
929
- path: `/api/v1/plans/${encodeURIComponent(id)}`
1081
+ /** Make an archived plan purchasable again (`isActive` → true). Safe to retry. */
1082
+ async unarchive(id) {
1083
+ const res = await this.transport.request({
1084
+ method: "POST",
1085
+ path: `/api/v1/plans/${encodeURIComponent(id)}/unarchive`,
1086
+ retry: true
930
1087
  });
1088
+ return res.plan;
931
1089
  }
932
1090
  };
933
1091
 
@@ -1011,15 +1169,12 @@ var Purchases = class {
1011
1169
  }
1012
1170
  };
1013
1171
 
1014
- // src/idempotency.ts
1015
- function newKey() {
1016
- const c = globalThis.crypto;
1017
- if (typeof c?.randomUUID === "function") return c.randomUUID();
1018
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
1019
- const r = Math.random() * 16 | 0;
1020
- const v = ch === "x" ? r : r & 3 | 8;
1021
- return v.toString(16);
1022
- });
1172
+ // src/customer-ref.ts
1173
+ function customerRefToWire(ref) {
1174
+ return {
1175
+ ...ref.customerId != null ? { customerId: ref.customerId } : {},
1176
+ ...ref.externalId != null ? { externalId: ref.externalId } : {}
1177
+ };
1023
1178
  }
1024
1179
 
1025
1180
  // src/resources/signals.ts
@@ -1035,14 +1190,16 @@ var USAGE_LOG_PATH = "/api/v1/signal/usage-log";
1035
1190
  function signalToWire(signal) {
1036
1191
  const t = signal.tokens;
1037
1192
  const body = {
1038
- customerId: signal.customerId,
1193
+ ...customerRefToWire(signal),
1039
1194
  // `usage` is required on all three model-priced kinds. input/output are
1040
- // always sent; `cacheTokens` only when provided (the server defaults 0).
1195
+ // always sent; `cacheTokens` / `cacheWriteTokens` only when provided (the
1196
+ // server defaults each to 0).
1041
1197
  usage: {
1042
1198
  model: signal.model,
1043
1199
  inputTokens: t.input,
1044
1200
  outputTokens: t.output,
1045
- ...t.cache != null ? { cacheTokens: t.cache } : {}
1201
+ ...t.cache != null ? { cacheTokens: t.cache } : {},
1202
+ ...t.cacheWrite != null ? { cacheWriteTokens: t.cacheWrite } : {}
1046
1203
  }
1047
1204
  };
1048
1205
  if (signal.member) body.member = signal.member;
@@ -1146,9 +1303,8 @@ var Signals = class {
1146
1303
  path: OUTCOME_COMPLETE_PATH,
1147
1304
  idempotencyKey,
1148
1305
  body: {
1149
- customerId: input.customerId,
1306
+ ...customerRefToWire(input),
1150
1307
  runId: input.runId,
1151
- complete: true,
1152
1308
  ...input.member ? { member: input.member } : {},
1153
1309
  idempotencyKey
1154
1310
  },
@@ -1163,23 +1319,6 @@ var Signals = class {
1163
1319
  ...handles(res)
1164
1320
  };
1165
1321
  }
1166
- /**
1167
- * Recent usage logs + totals for a customer. `GET /api/v1/usage`.
1168
- *
1169
- * Still the legacy path, deliberately. The `/api/v1/signal/*` family is
1170
- * POST-only — it replaced how usage is INGESTED, not how it is read — so
1171
- * `GET /api/v1/usage` has no successor and is not deprecated. It is also the
1172
- * only way to prove a signal landed, which matters more now that ingestion
1173
- * answers `202` for everything.
1174
- */
1175
- async list(params) {
1176
- const res = await this.transport.request({
1177
- method: "GET",
1178
- path: "/api/v1/usage",
1179
- query: { customerId: params.customerId, limit: params.limit }
1180
- });
1181
- return { customer: res.customer, totals: res.totals, logs: res.logs };
1182
- }
1183
1322
  /**
1184
1323
  * Record one unit-metered event (a fixed-price meter with no tokens),
1185
1324
  * addressed to a catalog unit by its `agentKey`. Sent immediately — unit
@@ -1199,7 +1338,7 @@ var Signals = class {
1199
1338
  path: UNIT_PATH,
1200
1339
  idempotencyKey,
1201
1340
  body: {
1202
- customerId: input.customerId,
1341
+ ...customerRefToWire(input),
1203
1342
  agentKey: input.agentKey,
1204
1343
  ...input.quantity != null ? { quantity: input.quantity } : {},
1205
1344
  ...input.member ? { member: input.member } : {},
@@ -1256,7 +1395,7 @@ var Signals = class {
1256
1395
  path: COMPOSITE_COMPLETE_PATH,
1257
1396
  idempotencyKey,
1258
1397
  body: {
1259
- customerId: input.customerId,
1398
+ ...customerRefToWire(input),
1260
1399
  composite: { [input.composite.ref]: input.composite.value },
1261
1400
  // Auto-assigned like the others. A retry here could not double-BILL
1262
1401
  // even without it — closing a closed occurrence is a server-side
@@ -1277,19 +1416,6 @@ var Signals = class {
1277
1416
  ...handles(res)
1278
1417
  };
1279
1418
  }
1280
- /**
1281
- * A customer's unit balances + recent unit events. `GET /api/v1/units`.
1282
- *
1283
- * Legacy path for the same reason as {@link list}: the signal family is
1284
- * POST-only, so the read side has no successor and is not deprecated.
1285
- */
1286
- unitUsage(params) {
1287
- return this.transport.request({
1288
- method: "GET",
1289
- path: "/api/v1/units",
1290
- query: { customerId: params.customerId, limit: params.limit }
1291
- });
1292
- }
1293
1419
  /**
1294
1420
  * One customer's usage history, merged from every meter kind — credit,
1295
1421
  * outcome, unit, wallet, and completed composite occurrences — into one
@@ -1329,148 +1455,88 @@ var Units = class {
1329
1455
  this.transport = transport;
1330
1456
  }
1331
1457
  transport;
1332
- /** List unit types. Pass `{ active: true }` to only return active ones.
1333
- * `{ active: false }` returns only archived ones; omitting it returns
1334
- * both. See `activeQuery` — the API itself only supports `true`. */
1458
+ /** List them. `{ active: true }` returns only active ones, `{ active: false }`
1459
+ * only archived ones; omit it for both. */
1335
1460
  async list(params = {}) {
1336
1461
  const res = await this.transport.request({
1337
1462
  method: "GET",
1338
- path: "/api/v1/units-catalog",
1463
+ path: "/api/v1/units",
1339
1464
  query: { active: activeQuery(params.active) }
1340
1465
  });
1341
1466
  return filterByActive(res.units, params.active);
1342
1467
  }
1343
- /** Create a unit type. `POST /api/v1/units-catalog`. */
1344
- async create(input) {
1345
- const res = await this.transport.request({
1346
- method: "POST",
1347
- path: "/api/v1/units-catalog",
1348
- body: input
1349
- });
1350
- return res.unit;
1351
- }
1352
- /** Fetch one unit type with stats. `GET /api/v1/units-catalog/:id`. */
1468
+ /** One unit's configuration. */
1353
1469
  async get(id) {
1354
1470
  const res = await this.transport.request({
1355
1471
  method: "GET",
1356
- path: `/api/v1/units-catalog/${encodeURIComponent(id)}`
1472
+ path: `/api/v1/units/${encodeURIComponent(id)}`
1357
1473
  });
1358
1474
  return res.unit;
1359
1475
  }
1360
- /** Update a unit type's definition. `PATCH /api/v1/units-catalog/:id`. */
1361
- async update(id, input) {
1476
+ /** Create one. It starts active. Not retried on failure — a retry could
1477
+ * create a second row. */
1478
+ async create(input) {
1362
1479
  const res = await this.transport.request({
1363
- method: "PATCH",
1364
- path: `/api/v1/units-catalog/${encodeURIComponent(id)}`,
1480
+ method: "POST",
1481
+ path: "/api/v1/units",
1365
1482
  body: input
1366
1483
  });
1367
1484
  return res.unit;
1368
1485
  }
1369
- /** Activate or deactivate a unit type. */
1370
- async setActive(id, isActive) {
1486
+ /** PARTIAL update: only the fields you send change. `tiers`, when sent, is
1487
+ * the complete new tier list. */
1488
+ async update(id, input) {
1371
1489
  const res = await this.transport.request({
1372
1490
  method: "PATCH",
1373
- path: `/api/v1/units-catalog/${encodeURIComponent(id)}`,
1374
- body: { isActive }
1491
+ path: `/api/v1/units/${encodeURIComponent(id)}`,
1492
+ body: input
1375
1493
  });
1376
1494
  return res.unit;
1377
1495
  }
1378
- /** Delete a unit type. */
1379
- async delete(id) {
1380
- await this.transport.request({
1381
- method: "DELETE",
1382
- path: `/api/v1/units-catalog/${encodeURIComponent(id)}`
1383
- });
1384
- }
1385
- };
1386
-
1387
- // src/resources/webhooks.ts
1388
- var Webhooks = class {
1389
- constructor(transport) {
1390
- this.transport = transport;
1391
- }
1392
- transport;
1393
- /** List configured webhooks. `GET /api/v1/webhooks`. */
1394
- async list() {
1395
- const res = await this.transport.request({
1396
- method: "GET",
1397
- path: "/api/v1/webhooks"
1398
- });
1399
- return res.webhooks;
1400
- }
1401
- /** Create or replace the webhook for a kind. `POST /api/v1/webhooks`. */
1402
- async upsert(input) {
1496
+ /** Retire it (`isActive` → false). Nothing is deleted. Safe to retry. */
1497
+ async archive(id) {
1403
1498
  const res = await this.transport.request({
1404
1499
  method: "POST",
1405
- path: "/api/v1/webhooks",
1406
- body: input
1407
- });
1408
- return res.webhook;
1409
- }
1410
- /** Delete the webhook for a kind (idempotent). `DELETE /api/v1/webhooks/:kind`. */
1411
- async delete(kind) {
1412
- await this.transport.request({
1413
- method: "DELETE",
1414
- path: `/api/v1/webhooks/${encodeURIComponent(kind)}`
1415
- });
1416
- }
1417
- };
1418
-
1419
- // src/resources/workspace.ts
1420
- var Workspace = class {
1421
- constructor(transport) {
1422
- this.transport = transport;
1423
- }
1424
- transport;
1425
- /** Identify the org + key behind the request, incl. `sandbox` | `live`. */
1426
- me() {
1427
- return this.transport.request({
1428
- method: "GET",
1429
- path: "/api/v1/me"
1500
+ path: `/api/v1/units/${encodeURIComponent(id)}/archive`,
1501
+ retry: true
1430
1502
  });
1503
+ return res.unit;
1431
1504
  }
1432
- /** The org's enabled models with live prices. `{ active: true }` returns only
1433
- * the models that can be metered right now.
1434
- * `{ active: false }` returns only archived ones; omitting it returns
1435
- * both. See `activeQuery` — the API itself only supports `true`. */
1436
- async models(params = {}) {
1505
+ /** Bring an archived one back (`isActive` → true). Safe to retry. */
1506
+ async unarchive(id) {
1437
1507
  const res = await this.transport.request({
1438
- method: "GET",
1439
- path: "/api/v1/models",
1440
- query: { active: activeQuery(params.active) }
1508
+ method: "POST",
1509
+ path: `/api/v1/units/${encodeURIComponent(id)}/unarchive`,
1510
+ retry: true
1441
1511
  });
1442
- return filterByActive(res.models, params.active);
1512
+ return res.unit;
1443
1513
  }
1444
1514
  };
1445
1515
 
1446
1516
  // src/client.ts
1447
1517
  var ClockNext = class {
1448
- /** Record + read usage signals (credit / wallet / outcome / unit). */
1518
+ /** Record usage signals (credit / wallet / outcome / unit) and read the usage log. */
1449
1519
  signals;
1450
- /** Manage customers, their members, wallet, and balances. */
1520
+ /** Manage customers, their members, and balances. */
1451
1521
  customers;
1452
- /** Define and manage billing plans. */
1522
+ /** Billing plans: list, get, create, update, archive, unarchive. */
1453
1523
  plans;
1524
+ /** Credits: list, get, create, update, archive, unarchive (no delete). */
1525
+ credits;
1526
+ /** Outcomes: list, get, create, update, archive, unarchive (no delete). */
1527
+ outcomes;
1528
+ /** Units: list, get, create, update, archive, unarchive (no delete). */
1529
+ units;
1530
+ /** Composites: list, get, create, update, archive, unarchive (no delete). */
1531
+ composites;
1454
1532
  /** Subscribe customers to plans (purchases). */
1455
1533
  purchases;
1456
1534
  /** Read invoices and mint hosted pay links. */
1457
1535
  invoices;
1458
1536
  /** Read payments (paid invoices). */
1459
1537
  payments;
1460
- /** Manage the credit-type catalog. */
1461
- credits;
1462
- /** Manage the composite catalog — bundles billed as one thing. */
1463
- composites;
1464
- /** Manage the outcome-type catalog. */
1465
- outcomes;
1466
- /** Manage the unit-type catalog. */
1467
- units;
1468
- /** Configure threshold webhooks. */
1469
- webhooks;
1470
1538
  /** Mint customer-portal embed tokens. */
1471
1539
  portal;
1472
- /** Identify the workspace (sandbox/live) + list its models. `/me`, `/models`. */
1473
- workspace;
1474
1540
  cfg;
1475
1541
  flusher;
1476
1542
  constructor(config) {
@@ -1485,16 +1551,14 @@ var ClockNext = class {
1485
1551
  this.signals = new Signals(transport, this.flusher, this.cfg);
1486
1552
  this.customers = new Customers(transport);
1487
1553
  this.plans = new Plans(transport);
1488
- this.purchases = new Purchases(transport);
1489
- this.invoices = new Invoices(transport);
1490
- this.payments = new Payments(transport);
1491
1554
  this.credits = new Credits(transport);
1492
- this.composites = new Composites(transport);
1493
1555
  this.outcomes = new Outcomes(transport);
1494
1556
  this.units = new Units(transport);
1495
- this.webhooks = new Webhooks(transport);
1557
+ this.composites = new Composites(transport);
1558
+ this.purchases = new Purchases(transport);
1559
+ this.invoices = new Invoices(transport);
1560
+ this.payments = new Payments(transport);
1496
1561
  this.portal = new Portal(transport);
1497
- this.workspace = new Workspace(transport);
1498
1562
  }
1499
1563
  /** Number of signals currently buffered (async mode). */
1500
1564
  get pending() {