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