Skip to content

feat: usage report transaction IDs and the usage history - #2

Draft
fuzcap wants to merge 1 commit into
mainfrom
feat/billing
Draft

fuzcap wants to merge 1 commit into
mainfrom
feat/billing

Conversation

@fuzcap

@fuzcap fuzcap commented Oct 6, 2026

Copy link
Copy Markdown

What does this PR change?

The JS SDKs' side of two Kaiten features: idempotent usage reports (transactionId) and the usage history. Server side: kaitencloud/kaiten#16; Go: kaitencloud/sdk-go#1.

@kaitencloud/client

  • ReportUsageInput.transactionId. reportUsage marks a keyed report idempotent, so the transport retries it on network errors and 5xx, every attempt under the same key. A report without a key is still sent once: a failed request may have been counted.
  • Errors. The reused-key 409 throws TransactionIdReusedError, a KaitenError with the original report in errors[0].value, never retried. A malformed key throws a TypeError before any request.
  • usage.reportDetailed / reportUsageDetailed also return replayed (Idempotent-Replayed) and metadataDropped (Kaiten-Metadata-Dropped). The transport gains runWithResponse to read them; run is unchanged.
  • Older APIs. An API without transactionId refuses the field (the body is closed). The report is resent without it, and the client stops sending keys (and retrying reports) for its lifetime, warning once.
  • usage.history / listUsageReports. An instance's entitlement usage reports, oldest first, following the afterSeq pages, with from, to, transactionId and a limit.

@kaitencloud/server

  • The regenerated contract adds transactionId to the usage report body, plus Instances.listUsageReports, exportUsageReports and exportOrganizationUsageReports.
  • isTransactionIdReused sits beside isThresholdExceeded. The README shows the history operations and how to stream an export (parseAs: "stream").

Why?

A usage report that timed out could not be retried safely, because it might count twice. A key makes the retry safe. The history lets a host show and keep what was counted.

Testing

  • vp check and the affected packages' tests pass. vp run -r build && vp check && vp run -r test: client 179, server 35, examples/node 5. Bundle size and version invariants pass.
  • A changeset is included (minor for both packages).

New tests:

  • Client report: the key is sent only when given. A 503 then 200 with a key gives two attempts under one key; without a key, one. A reused key throws TransactionIdReusedError with no retry. Malformed keys are refused with no request.
  • Client flags: the detailed result reads both headers.
  • Older API: its 422 on body.transactionId gives a resend without the key, later reports keyless, and one warning. A newer API's InvalidTransactionId is not mistaken for it.
  • Client history: the afterSeq walk, the limit, and OutsideRetention surfacing with its code.
  • Server: the key is sent in the body, isTransactionIdReused is distinct from the threshold predicate, the history query, and a streamed CSV export.

check:openapi-drift fails until the Kaiten release carrying these operations publishes @kaitencloud/openapi: the snapshot here is synced from that release's spec, ahead of the published one.

Compatibility / migration impact

Additive. reportUsage keeps its signature and return type. KaitenUsageModule gains reportDetailed and history, which matters only to code implementing that interface itself. KaitenClientLike is unchanged. Keys need a Kaiten release with transactionId; against an older one the client falls back as described above.

Checklist

  • I have read CONTRIBUTING.md.
  • Every commit in this PR includes a valid DCO Signed-off-by line.
  • I have the right to submit all code, documentation, assets, and other
    material included in this PR.
  • I have not included secrets, credentials, customer data, or confidential
    information.
  • I have identified relevant third-party code/assets and preserved required
    licenses and attributions.
  • I have added or updated tests where appropriate.
  • I have updated documentation where appropriate.
  • I understand that accepted contributions to the Kaiten open-source core
    are contributed under Apache-2.0.

Kaiten now makes usage reports idempotent with a client key and keeps
their history.

- contracts/openapi.yaml is synced from the Kaiten release that adds them,
  and both generated clients are regenerated.
- @kaitencloud/client: ReportUsageInput.transactionId. reportUsage marks a
  keyed report idempotent, so the transport retries it on network errors
  and 5xx under the same key; a report without a key is still sent once.
  A malformed key throws a TypeError before any request; the reused-key
  409 throws TransactionIdReusedError (a KaitenError), never retried.
- usage.reportDetailed / reportUsageDetailed also return replayed
  (Idempotent-Replayed) and metadataDropped (Kaiten-Metadata-Dropped); the
  transport gains runWithResponse to read them.
- An API without transactionId refuses the field: the report is resent
  without it and the client stops sending keys for its lifetime, warning
  once.
- usage.history / listUsageReports walks an instance's entitlement usage
  reports by afterSeq, with from, to, transactionId and a limit.
- @kaitencloud/server: isTransactionIdReused, and the README shows the
  history operations and how to stream an export.

Signed-off-by: Tom Ribuot <tom.ribuot@kaiten.sh>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant