@clocknext/sdk 0.14.0 → 0.15.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.
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * Basic usage: record a few signals from a long-lived Node process.
3
- * Run with: CLOCKNEXT_API_KEY=cnk_… npx tsx examples/node-basic.ts
4
3
  *
5
4
  * Note the discovery step first. `customerId`, `model` and `agentKey` are not
6
5
  * strings you can invent — they name rows that must already exist in your
7
6
  * workspace, and a signal naming one that does not is rejected. So this example
8
- * reads them out of the API rather than hard-coding placeholders that are
9
- * guaranteed to 422 on someone else's workspace.
7
+ * reads the customer and its agent keys out of the API rather than hard-coding
8
+ * placeholders that are guaranteed to 422 on someone else's workspace. The
9
+ * model id comes from Settings → Models in the dashboard:
10
+ *
11
+ * CLOCKNEXT_API_KEY=cnk_… CLOCKNEXT_MODEL=gpt-4o npx tsx examples/node-basic.ts
10
12
  */
11
13
  import { ClockNext } from "@clocknext/sdk";
12
14
 
@@ -18,34 +20,28 @@ const cnk = new ClockNext({
18
20
  });
19
21
 
20
22
  async function main() {
21
- // 0. Ground the integration. Check WHICH workspace this key points at before
22
- // sending anything — a live key bills for real.
23
- const { organization } = await cnk.workspace.me();
24
- console.log(`workspace: ${organization.name} (${organization.environment})`);
25
-
26
- // 1. Discover real ids instead of guessing them.
27
- const [models, credits, customers] = await Promise.all([
28
- cnk.workspace.models({ active: true }),
29
- cnk.credits.list({ active: true }),
30
- cnk.customers.list({ limit: 1 }),
31
- ]);
32
-
33
- const model = models[0];
34
- const credit = credits[0];
35
- const customer = customers.customers[0];
36
- if (!model || !credit || !customer) {
23
+ // 1. Discover real ids instead of guessing them. A customer's balances list
24
+ // every credit and outcome step its plan sells, each with its agentKey.
25
+ const model = process.env.CLOCKNEXT_MODEL;
26
+ const customer = (await cnk.customers.list({ limit: 1 })).customers[0];
27
+ if (!model || !customer) {
37
28
  throw new Error(
38
- "This workspace needs at least one enabled model, one credit type, and one customer before it can meter anything.",
29
+ "Set CLOCKNEXT_MODEL to a model enabled under Settings → Models, and create at least one customer, before metering anything.",
39
30
  );
40
31
  }
41
- console.log(`metering ${credit.agentKey} on ${model.modelId}`);
32
+ const balances = await cnk.customers.balances(customer.id);
33
+ const credit = balances.credits[0];
34
+ if (!credit) {
35
+ throw new Error("This customer's plan needs at least one credit before it can meter credit usage.");
36
+ }
37
+ console.log(`metering ${credit.agentKey} on ${model}`);
42
38
 
43
39
  // 2. Credit usage — metered against a credit by its stable `agentKey`, priced
44
40
  // by tokens. Optionally group it into a composite occurrence, billed once
45
41
  // on close.
46
42
  await cnk.signals.credit({
47
43
  customerId: customer.id,
48
- model: model.modelId,
44
+ model,
49
45
  agentKey: credit.agentKey,
50
46
  tokens: { input: 1200, output: 340, cache: 0 },
51
47
  custom: { feature: "summarize", tier: "pro" },
@@ -54,23 +50,19 @@ async function main() {
54
50
  // 3. Wallet usage (debits the customer's USD wallet at model cost).
55
51
  await cnk.signals.wallet({
56
52
  customerId: customer.id,
57
- model: model.modelId,
53
+ model,
58
54
  tokens: { input: 800, output: 5000 },
59
55
  });
60
56
 
61
57
  // 4. Outcome run — every step shares a `runId`; a separate, dedicated call
62
58
  // (`completeOutcome`) closes and bills the run — never a flag on a step.
63
- // `agentKey` is the OutcomeStep's key, which you read off
64
- // `cnk.outcomes.list()` the same way.
65
- // `outcomes.list()` returns catalogue rows WITHOUT their steps; the step
66
- // agent keys — which is what a signal reports against — are on the detail.
67
- const outcomeRow = (await cnk.outcomes.list({ active: true }))[0];
68
- const outcome = outcomeRow ? await cnk.outcomes.get(outcomeRow.id) : undefined;
59
+ // `agentKey` is the OutcomeStep's key, read off the same balances.
60
+ const outcome = balances.outcomes[0];
69
61
  if (outcome?.steps[0]) {
70
62
  const runId = `demo_run_${Date.now()}`;
71
63
  await cnk.signals.outcome({
72
64
  customerId: customer.id,
73
- model: model.modelId,
65
+ model,
74
66
  agentKey: outcome.steps[0].agentKey,
75
67
  runId,
76
68
  tokens: { input: 600, output: 150 },
@@ -83,8 +75,8 @@ async function main() {
83
75
  // asynchronous, so this — not the track() return value — is what proves a
84
76
  // signal actually landed.
85
77
  await cnk.flush();
86
- const usage = await cnk.signals.list({ customerId: customer.id, limit: 10 });
87
- console.log("recent logs:", usage.logs.length, "totals:", usage.totals);
78
+ const page = await cnk.signals.usageLog({ customerId: customer.id, limit: 10 });
79
+ console.log("recent logs:", page.usageLogs.length);
88
80
 
89
81
  await cnk.close();
90
82
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clocknext/sdk",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Official ClockNext SDK — record usage signals and manage plans, purchases, invoices, customers, and billing from your backend.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/client.ts CHANGED
@@ -1,19 +1,13 @@
1
1
  import { resolveConfig } from "./config.js";
2
2
  import { Flusher } from "./flusher.js";
3
3
  import { Transport } from "./http.js";
4
- import { Composites } from "./resources/composites.js";
5
- import { Credits } from "./resources/credits.js";
6
4
  import { Customers } from "./resources/customers.js";
7
5
  import { Invoices } from "./resources/invoices.js";
8
- import { Outcomes } from "./resources/outcomes.js";
9
6
  import { Payments } from "./resources/payments.js";
10
7
  import { Plans } from "./resources/plans.js";
11
8
  import { Portal } from "./resources/portal.js";
12
9
  import { Purchases } from "./resources/purchases.js";
13
10
  import { Signals } from "./resources/signals.js";
14
- import { Units } from "./resources/units.js";
15
- import { Webhooks } from "./resources/webhooks.js";
16
- import { Workspace } from "./resources/workspace.js";
17
11
  import type { ClockNextConfig, ResolvedConfig } from "./types.js";
18
12
 
19
13
  /**
@@ -34,11 +28,11 @@ import type { ClockNextConfig, ResolvedConfig } from "./types.js";
34
28
  * ```
35
29
  */
36
30
  export class ClockNext {
37
- /** Record + read usage signals (credit / wallet / outcome / unit). */
31
+ /** Record usage signals (credit / wallet / outcome / unit) and read the usage log. */
38
32
  readonly signals: Signals;
39
- /** Manage customers, their members, wallet, and balances. */
33
+ /** Manage customers, their members, and balances. */
40
34
  readonly customers: Customers;
41
- /** Define and manage billing plans. */
35
+ /** List billing plans. */
42
36
  readonly plans: Plans;
43
37
  /** Subscribe customers to plans (purchases). */
44
38
  readonly purchases: Purchases;
@@ -46,20 +40,8 @@ export class ClockNext {
46
40
  readonly invoices: Invoices;
47
41
  /** Read payments (paid invoices). */
48
42
  readonly payments: Payments;
49
- /** Manage the credit-type catalog. */
50
- readonly credits: Credits;
51
- /** Manage the composite catalog — bundles billed as one thing. */
52
- readonly composites: Composites;
53
- /** Manage the outcome-type catalog. */
54
- readonly outcomes: Outcomes;
55
- /** Manage the unit-type catalog. */
56
- readonly units: Units;
57
- /** Configure threshold webhooks. */
58
- readonly webhooks: Webhooks;
59
43
  /** Mint customer-portal embed tokens. */
60
44
  readonly portal: Portal;
61
- /** Identify the workspace (sandbox/live) + list its models. `/me`, `/models`. */
62
- readonly workspace: Workspace;
63
45
 
64
46
  private readonly cfg: ResolvedConfig;
65
47
  private readonly flusher: Flusher;
@@ -81,13 +63,7 @@ export class ClockNext {
81
63
  this.purchases = new Purchases(transport);
82
64
  this.invoices = new Invoices(transport);
83
65
  this.payments = new Payments(transport);
84
- this.credits = new Credits(transport);
85
- this.composites = new Composites(transport);
86
- this.outcomes = new Outcomes(transport);
87
- this.units = new Units(transport);
88
- this.webhooks = new Webhooks(transport);
89
66
  this.portal = new Portal(transport);
90
- this.workspace = new Workspace(transport);
91
67
  }
92
68
 
93
69
  /** Number of signals currently buffered (async mode). */
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The customer half of a request body: `customerId` and/or `externalId`,
3
+ * each only when set. Both are forwarded as given — the server applies the
4
+ * precedence (`customerId` wins), so the SDK never second-guesses it.
5
+ */
6
+ export function customerRefToWire(ref: {
7
+ customerId?: string;
8
+ externalId?: string;
9
+ }): { customerId?: string; externalId?: string } {
10
+ return {
11
+ ...(ref.customerId != null ? { customerId: ref.customerId } : {}),
12
+ ...(ref.externalId != null ? { externalId: ref.externalId } : {}),
13
+ };
14
+ }
package/src/errors.ts CHANGED
@@ -154,7 +154,11 @@ export function translateFieldPaths(message: string): string {
154
154
  * toward it still classifies, never as the sole test.
155
155
  */
156
156
  const ALLOWANCE_MESSAGE =
157
- /\binsufficient\b|\bno allowance left\b|\bno slots left\b|\boutcomes remaining\b|\btop up the (?:allowance|balance)\b/i;
157
+ /\binsufficient\b|\bno allowance left\b|\bno slots left\b|\boutcomes remaining\b|\btop up the (?:allowance|balance)\b|\bbelow zero\b/i;
158
+
159
+ /** The adjust API's 422 for an idempotency key reused with a DIFFERENT
160
+ * request. A conflict, not a plan problem — and never worth retrying. */
161
+ const IDEMPOTENCY_KEY_REUSED = /\bidempotency key was already used\b/i;
158
162
 
159
163
  export function isAllowanceFailure(message: string, code?: string): boolean {
160
164
  if (code) return code === "ALLOWANCE_EXHAUSTED";
@@ -205,6 +209,9 @@ export function errorFromResponse(
205
209
  });
206
210
  }
207
211
  if (status === 422) {
212
+ if (IDEMPOTENCY_KEY_REUSED.test(raw)) {
213
+ return new ConflictError(raw, { ...opts, retryable: false });
214
+ }
208
215
  return isAllowanceFailure(raw, code)
209
216
  ? new AllowanceError(raw, opts)
210
217
  : new PlanError(raw, opts);
package/src/filters.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The `active` list filter, shared by every catalog resource.
2
+ * The `active` list filter, used by `plans.list()`.
3
3
  *
4
4
  * The API offers exactly ONE filter: `?active=true` narrows a list to active
5
5
  * rows. There is no inactive-only parameter — the server tests
package/src/index.ts CHANGED
@@ -47,7 +47,6 @@ export type {
47
47
  OutcomeCompletionInput,
48
48
  OutcomeCompletionResult,
49
49
  TrackResult,
50
- UsageList,
51
50
  SerializedUsageLog,
52
51
  UsageBalance,
53
52
  SerializedRuleApplication,
@@ -56,8 +55,6 @@ export type {
56
55
  UnitTrackResult,
57
56
  CompositeCompletionInput,
58
57
  CompositeCompletionResult,
59
- UnitUsageRow,
60
- UnitUsageList,
61
58
  SerializedUnitUsage,
62
59
  UsageLogPeriod,
63
60
  UsageLogListParams,
@@ -65,10 +62,8 @@ export type {
65
62
  UsageLogOutcomeAttribution,
66
63
  UsageLogEntry,
67
64
  UsageLogPage,
68
- // workspace
69
- WorkspaceIdentity,
70
- Model,
71
65
  // customers
66
+ CustomerRef,
72
67
  ApiCustomer,
73
68
  CustomerProfileInput,
74
69
  CreateCustomerInput,
@@ -85,30 +80,19 @@ export type {
85
80
  RemovedMember,
86
81
  AddMemberInput,
87
82
  UpdateMemberInput,
88
- // wallet & balance adjustments
89
- WalletTransaction,
90
- WalletTransactionType,
91
- WalletTransactionSource,
92
- WalletEntitlementKind,
93
- WalletList,
94
- WalletEntryInput,
83
+ // balance adjustments
84
+ WalletAdjustInput,
85
+ AdjustResult,
86
+ WalletPool,
95
87
  CreditAdjustInput,
96
88
  OutcomeAdjustInput,
97
89
  UnitAdjustInput,
98
90
  CompositeAdjustInput,
91
+ RecurringGrantInput,
92
+ RecurringGrantResult,
99
93
  // plans
100
94
  Plan,
101
- PlanDetail,
102
95
  PlanComponent,
103
- PlanComponentInput,
104
- WalletComponentInput,
105
- FlatComponentInput,
106
- CreditComponentInput,
107
- OutcomeComponentInput,
108
- UnitComponentInput,
109
- CompositeComponentInput,
110
- CreatePlanInput,
111
- UpdatePlanInput,
112
96
  BillingCycle,
113
97
  PlanType,
114
98
  PlanKind,
@@ -132,42 +116,6 @@ export type {
132
116
  // payments
133
117
  PaymentRow,
134
118
  PaymentDetail,
135
- // cost analytics (credits / outcomes)
136
- CostPoint,
137
- CostStats,
138
- CreditAnalytics,
139
- OutcomeAnalytics,
140
- // credits catalog
141
- Credit,
142
- CreditDetail,
143
- CreateCreditInput,
144
- UpdateCreditInput,
145
- ModelBundleEntry,
146
- // outcomes catalog
147
- Outcome,
148
- OutcomeDetail,
149
- OutcomeStepInput,
150
- CreateOutcomeInput,
151
- UpdateOutcomeInput,
152
- // composites catalog
153
- Composite,
154
- CompositeCatalogueRef,
155
- CreateCompositeInput,
156
- // units catalog
157
- Unit,
158
- UnitDetail,
159
- UnitCustomerRow,
160
- UnitTier,
161
- UnitTierInput,
162
- UnitPricingType,
163
- CreateUnitInput,
164
- FlatUnitInput,
165
- TieredUnitInput,
166
- UpdateUnitInput,
167
- // webhooks
168
- Webhook,
169
- UpsertWebhookInput,
170
- ThresholdKind,
171
119
  // portal
172
120
  PortalTokenInput,
173
121
  PortalToken,