@usagetap/sdk 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -385,7 +385,8 @@ const { begin, end, vendor, endUsage } = envelope.data;
385
385
 
386
386
  Key exports from `@usagetap/sdk`:
387
387
 
388
- - `UsageTapClient` – minimal HTTP client for `call_begin` and `call_end`.
388
+ - `UsageTapClient` – minimal HTTP client for `call_begin`, `call_end`, and `checkUsage`.
389
+ - `checkUsage` – lightweight method to query current usage status without creating a call session.
389
390
  - `wrapOpenAI` – wraps an OpenAI client instance with automatic begin/end handling.
390
391
  - `wrapFetch` – wraps a fetch function to automatically instrument OpenAI API calls (minimal integration).
391
392
  - `withUsage` / `withUsageMiddleware` – Express middleware for server-side usage tracking (requires `express` peer dependency).
@@ -396,6 +397,21 @@ Key exports from `@usagetap/sdk`:
396
397
 
397
398
  All helpers are designed for server runtimes. Use `UsageTapClient` with `allowBrowser: true` only for sandbox/test scenarios.
398
399
 
400
+ ### Check usage without creating a call
401
+
402
+ When you need to display current quota status, plan details, or remaining balances without tracking a vendor call, use `checkUsage()`:
403
+
404
+ ```ts
405
+ const usageStatus = await usageTap.checkUsage({ customerId: "cust_123" });
406
+
407
+ console.log("Meters:", usageStatus.data.meters);
408
+ console.log("Allowed:", usageStatus.data.allowed);
409
+ console.log("Plan:", usageStatus.data.plan);
410
+ console.log("Balances:", usageStatus.data.balances);
411
+ ```
412
+
413
+ This returns the same rich usage snapshot as `call_begin` (meters, entitlements, subscription details, plan info, balances) but without creating a call record. Use this for dashboard widgets, pre-flight checks, or displaying quota status to users.
414
+
399
415
  ## Response envelope (canonical only)
400
416
 
401
417
  UsageTap responds exclusively with the canonical `{ result, data, correlationId }` envelope for every endpoint. The SDK automatically sends `Accept: application/vnd.usagetap.v1+json`, parses the envelope, and returns strongly typed data structures. Transitional `raw` payloads and the `normalize*` helpers have been removed—`response.data` already contains the canonical shape you should persist or render.
@@ -516,6 +532,29 @@ UsageTap responds exclusively with the canonical `{ result, data, correlationId
516
532
 
517
533
  `metered` is derived from the raw Dynamo deltas. Additional meters (audio seconds, reasoning tokens, balances) will populate in later phases without breaking the contract.
518
534
 
535
+ ### Premium detection and override
536
+
537
+ UsageTap automatically determines whether a call is premium based on the model's output token pricing:
538
+ - If the output token price exceeds **$4.00 per million tokens**, the call is classified as premium
539
+ - Otherwise, it's classified as standard
540
+
541
+ You can explicitly override this detection by passing `isPremium` in your `call_end` request:
542
+
543
+ ```ts
544
+ await usageTap.endCall({
545
+ callId: begin.data.callId,
546
+ modelUsed: "custom-model-v2",
547
+ inputTokens: 100,
548
+ responseTokens: 200,
549
+ isPremium: true, // Explicitly mark this as a premium call
550
+ });
551
+ ```
552
+
553
+ This is useful when:
554
+ - You're using custom models that aren't in UsageTap's pricing database
555
+ - You want to enforce specific billing tiers regardless of pricing
556
+ - You're implementing your own tier classification logic
557
+
519
558
  ### Raw fetch integrations
520
559
 
521
560
  Prefer `UsageTapClient` whenever possible—it handles retries, headers, and idempotency for you. If you still need to work with `fetch` directly, remember to request the canonical media type and consume the envelope shape directly:
@@ -1,2 +1,2 @@
1
1
  import 'openai';
2
- export { N as NodeResponseLike, O as OpenAIAdapter, b as OpenAIAdapterInit, d as OpenAIInvokeParams, e as OpenAIInvokeResult, Y as OpenAIRequestContext, Z as OpenAIStreamCallResult, f as OpenAIStreamParams, g as OpenAIStreamResult, S as StreamMode, l as StreamOpenAIRouteOptions, h as StreamToResponseOptions, _ as UsageTapStream, i as WrapOpenAICallOptions, W as WrapOpenAIContext, a as WrapOpenAIOptions, j as WrapOpenAIResponseCallOptions, k as WrappedOpenAI, c as createOpenAIAdapter, p as pipeToResponse, s as streamOpenAIRoute, t as toNextResponse, w as wrapOpenAI } from '../openai-CKyw08rB.cjs';
2
+ export { N as NodeResponseLike, O as OpenAIAdapter, b as OpenAIAdapterInit, d as OpenAIInvokeParams, e as OpenAIInvokeResult, $ as OpenAIRequestContext, a0 as OpenAIStreamCallResult, f as OpenAIStreamParams, g as OpenAIStreamResult, S as StreamMode, l as StreamOpenAIRouteOptions, h as StreamToResponseOptions, a1 as UsageTapStream, i as WrapOpenAICallOptions, W as WrapOpenAIContext, a as WrapOpenAIOptions, j as WrapOpenAIResponseCallOptions, k as WrappedOpenAI, c as createOpenAIAdapter, p as pipeToResponse, s as streamOpenAIRoute, t as toNextResponse, w as wrapOpenAI } from '../openai-CeptbEGH.cjs';
@@ -1,2 +1,2 @@
1
1
  import 'openai';
2
- export { N as NodeResponseLike, O as OpenAIAdapter, b as OpenAIAdapterInit, d as OpenAIInvokeParams, e as OpenAIInvokeResult, Y as OpenAIRequestContext, Z as OpenAIStreamCallResult, f as OpenAIStreamParams, g as OpenAIStreamResult, S as StreamMode, l as StreamOpenAIRouteOptions, h as StreamToResponseOptions, _ as UsageTapStream, i as WrapOpenAICallOptions, W as WrapOpenAIContext, a as WrapOpenAIOptions, j as WrapOpenAIResponseCallOptions, k as WrappedOpenAI, c as createOpenAIAdapter, p as pipeToResponse, s as streamOpenAIRoute, t as toNextResponse, w as wrapOpenAI } from '../openai-CKyw08rB.js';
2
+ export { N as NodeResponseLike, O as OpenAIAdapter, b as OpenAIAdapterInit, d as OpenAIInvokeParams, e as OpenAIInvokeResult, $ as OpenAIRequestContext, a0 as OpenAIStreamCallResult, f as OpenAIStreamParams, g as OpenAIStreamResult, S as StreamMode, l as StreamOpenAIRouteOptions, h as StreamToResponseOptions, a1 as UsageTapStream, i as WrapOpenAICallOptions, W as WrapOpenAIContext, a as WrapOpenAIOptions, j as WrapOpenAIResponseCallOptions, k as WrappedOpenAI, c as createOpenAIAdapter, p as pipeToResponse, s as streamOpenAIRoute, t as toNextResponse, w as wrapOpenAI } from '../openai-CeptbEGH.js';
@@ -1,5 +1,5 @@
1
1
  import OpenAI from 'openai';
2
- import { U as UsageTapClient, O as OpenAIAdapter } from '../openai-CKyw08rB.cjs';
2
+ import { U as UsageTapClient, O as OpenAIAdapter } from '../openai-CeptbEGH.cjs';
3
3
 
4
4
  interface OpenRouterAdapterInit {
5
5
  client: OpenAI;
@@ -1,5 +1,5 @@
1
1
  import OpenAI from 'openai';
2
- import { U as UsageTapClient, O as OpenAIAdapter } from '../openai-CKyw08rB.js';
2
+ import { U as UsageTapClient, O as OpenAIAdapter } from '../openai-CeptbEGH.js';
3
3
 
4
4
  interface OpenRouterAdapterInit {
5
5
  client: OpenAI;
package/dist/index.cjs CHANGED
@@ -118,6 +118,7 @@ async function runWithRetry(operation, options, shouldRetry, onSchedule, signal)
118
118
  // src/client.ts
119
119
  var CALL_BEGIN_PATH = "call_begin";
120
120
  var CALL_END_PATH = "call_end";
121
+ var CHECK_USAGE_PATH = "customers/{customerId}/usage";
121
122
  var AUTH_HEADER = "authorization";
122
123
  var API_KEY_HEADER = "x-api-key";
123
124
  var CORRELATION_HEADER = "x-usage-correlation-id";
@@ -125,7 +126,7 @@ var IDEMPOTENCY_HEADER = "idempotency-key";
125
126
  var SDK_HEADER = "x-usage-sdk";
126
127
  var USER_AGENT = "UsageTapClient";
127
128
  var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
128
- var SDK_VERSION = "0.1.0" ;
129
+ var SDK_VERSION = "0.2.1" ;
129
130
  var HAS_WINDOW = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
130
131
  var UsageTapClient = class {
131
132
  apiKey;
@@ -219,6 +220,23 @@ var UsageTapClient = class {
219
220
  );
220
221
  return response;
221
222
  }
223
+ async checkUsage(request, options = {}) {
224
+ if (!request?.customerId) {
225
+ throw new UsageTapError(
226
+ "USAGETAP_BAD_REQUEST",
227
+ "checkUsage requires customerId"
228
+ );
229
+ }
230
+ const path = CHECK_USAGE_PATH.replace(
231
+ "{customerId}",
232
+ encodeURIComponent(request.customerId)
233
+ );
234
+ const response = await this.requestGet(
235
+ path,
236
+ options
237
+ );
238
+ return response;
239
+ }
222
240
  async withUsage(beginRequest, handler, options = {}) {
223
241
  const idempotencyKey = beginRequest.idempotency ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
224
242
  const beginPayload = idempotencyKey ? { ...beginRequest, idempotency: idempotencyKey } : { ...beginRequest };
@@ -338,6 +356,62 @@ var UsageTapClient = class {
338
356
  throw error;
339
357
  });
340
358
  }
359
+ async requestGet(path, options) {
360
+ const url = new URL(path, this.baseUrl).toString();
361
+ const headers = this.composeHeaders(void 0, options);
362
+ const resolvedRetry = resolveRetryOptions(
363
+ this.retryDefaults,
364
+ options.retries
365
+ );
366
+ const startTime = () => typeof performance !== "undefined" ? performance.now() : Date.now();
367
+ return runWithRetry(
368
+ async (attempt) => {
369
+ const startedAt = startTime();
370
+ this.log({
371
+ event: "request:start",
372
+ path,
373
+ attempt,
374
+ correlationId: options.correlationId
375
+ });
376
+ const response = await this.performFetch({
377
+ url,
378
+ method: "GET",
379
+ headers,
380
+ signal: options.signal
381
+ });
382
+ this.log({
383
+ event: "request:success",
384
+ path,
385
+ attempt,
386
+ correlationId: response.correlationId,
387
+ elapsedMs: startTime() - startedAt
388
+ });
389
+ return response;
390
+ },
391
+ resolvedRetry,
392
+ (error) => this.shouldRetry(error),
393
+ (attempt, delayMs, error) => {
394
+ this.log({
395
+ event: "retry:scheduled",
396
+ path,
397
+ attempt,
398
+ correlationId: options.correlationId,
399
+ error,
400
+ elapsedMs: delayMs
401
+ });
402
+ },
403
+ options.signal
404
+ ).catch((error) => {
405
+ this.log({
406
+ event: "retry:exhausted",
407
+ path,
408
+ attempt: resolvedRetry.maxAttempts,
409
+ correlationId: options.correlationId,
410
+ error
411
+ });
412
+ throw error;
413
+ });
414
+ }
341
415
  async performFetch(init) {
342
416
  let response;
343
417
  try {