@usagetap/sdk 1.2.0 → 1.3.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 +200 -225
- package/dist/adapters/anthropic.cjs +1609 -5
- package/dist/adapters/anthropic.cjs.map +1 -1
- package/dist/adapters/anthropic.d.cts +30 -2
- package/dist/adapters/anthropic.d.ts +30 -2
- package/dist/adapters/anthropic.mjs +1608 -6
- package/dist/adapters/anthropic.mjs.map +1 -1
- package/dist/adapters/openai.cjs +1659 -5
- package/dist/adapters/openai.cjs.map +1 -1
- package/dist/adapters/openai.d.cts +37 -2
- package/dist/adapters/openai.d.ts +37 -2
- package/dist/adapters/openai.mjs +1658 -6
- package/dist/adapters/openai.mjs.map +1 -1
- package/dist/adapters/openrouter.cjs.map +1 -1
- package/dist/adapters/openrouter.d.cts +1 -1
- package/dist/adapters/openrouter.d.ts +1 -1
- package/dist/adapters/openrouter.mjs.map +1 -1
- package/dist/anthropic/index.cjs +1609 -5
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.d.cts +2 -2
- package/dist/anthropic/index.d.ts +2 -2
- package/dist/anthropic/index.mjs +1608 -6
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/{client-EMXt9_fA.d.cts → client-BD8O2J8Z.d.cts} +30 -4
- package/dist/{client-EMXt9_fA.d.ts → client-BD8O2J8Z.d.ts} +30 -4
- package/dist/express/index.cjs +19 -0
- package/dist/express/index.cjs.map +1 -1
- package/dist/express/index.d.cts +1 -1
- package/dist/express/index.d.ts +1 -1
- package/dist/express/index.mjs +19 -0
- package/dist/express/index.mjs.map +1 -1
- package/dist/index.cjs +48 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +48 -20
- package/dist/index.mjs.map +1 -1
- package/dist/openai/index.cjs +1659 -5
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.d.cts +2 -2
- package/dist/openai/index.d.ts +2 -2
- package/dist/openai/index.mjs +1658 -6
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openrouter/index.cjs +3024 -0
- package/dist/openrouter/index.cjs.map +1 -0
- package/dist/openrouter/index.d.cts +4 -0
- package/dist/openrouter/index.d.ts +4 -0
- package/dist/openrouter/index.mjs +3019 -0
- package/dist/openrouter/index.mjs.map +1 -0
- package/package.json +102 -44
package/README.md
CHANGED
|
@@ -8,205 +8,180 @@ Server-only JavaScript/TypeScript client for UsageTap. The SDK helps you instrum
|
|
|
8
8
|
|
|
9
9
|
Optional adapters live behind subpath exports so their peer dependencies stay out of the core bundle:
|
|
10
10
|
|
|
11
|
-
- `@usagetap/sdk/openai` – OpenAI/OpenRouter helpers (`wrapOpenAI`, `streamOpenAIRoute`, etc.)
|
|
12
|
-
- `@usagetap/sdk/anthropic` – Anthropic helper (`wrapAnthropic`)
|
|
13
|
-
- `@usagetap/sdk/
|
|
14
|
-
- `@usagetap/sdk/
|
|
11
|
+
- `@usagetap/sdk/openai` – OpenAI/OpenRouter helpers (`wrapOpenAI`, `streamOpenAIRoute`, etc.)
|
|
12
|
+
- `@usagetap/sdk/anthropic` – Anthropic helper (`wrapAnthropic`)
|
|
13
|
+
- `@usagetap/sdk/openrouter` – discoverable OpenRouter aliases for the OpenAI-compatible wrappers
|
|
14
|
+
- `@usagetap/sdk/express` – Express middleware
|
|
15
|
+
- `@usagetap/sdk/react` – React chat hook
|
|
15
16
|
|
|
16
17
|
Install only the peer dependencies for the adapters you actually use.
|
|
17
18
|
|
|
18
19
|
## Quick start
|
|
19
20
|
|
|
20
|
-
Install the peer dependency for your vendor (e.g. `openai` or `@anthropic-ai/sdk`) and the UsageTap SDK in your server runtime.
|
|
21
|
+
Install the peer dependency for your vendor (e.g. `openai` or `@anthropic-ai/sdk`) and the UsageTap SDK in your server runtime.
|
|
21
22
|
|
|
22
23
|
```bash
|
|
23
24
|
npm install @usagetap/sdk openai
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
Wrap the provider client you already use. `USAGETAP_API_KEY` and the production
|
|
28
|
+
UsageTap URL are read automatically:
|
|
27
29
|
|
|
28
30
|
```ts
|
|
29
31
|
import OpenAI from "openai";
|
|
30
|
-
import {
|
|
32
|
+
import { withMetering } from "@usagetap/sdk/openai";
|
|
31
33
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
const openai = withMetering(new OpenAI(), "cust_123");
|
|
35
|
+
const completion = await openai.responses.create({
|
|
36
|
+
model: "gpt-5.5-mini",
|
|
37
|
+
input: "Draft a welcome email for our Pro plan",
|
|
35
38
|
});
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
console.log(completion.output_text);
|
|
41
|
+
```
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
Only the customer ID is required for metering. Pass an object instead of the
|
|
44
|
+
string when you want optional feature, tag, entitlement, or prompt-compression
|
|
45
|
+
settings. Existing `wrapOpenAI`, `wrapAnthropic`, and manual `withUsage` flows
|
|
46
|
+
remain supported for advanced control.
|
|
47
|
+
|
|
48
|
+
For standalone compression, wrap the client without changing any downstream
|
|
49
|
+
calls:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import OpenAI from "openai";
|
|
53
|
+
import { withCompression } from "@usagetap/sdk/openai";
|
|
54
|
+
|
|
55
|
+
const openai = withCompression(new OpenAI(), {
|
|
56
|
+
// Defaults to 1,000. Use 0 to always attempt compression.
|
|
57
|
+
minContextTokens: 2_000,
|
|
58
|
+
});
|
|
59
|
+
const completion = await openai.responses.create({
|
|
60
|
+
model: "gpt-5.5-mini",
|
|
61
|
+
input: longPrompt,
|
|
43
62
|
});
|
|
63
|
+
```
|
|
44
64
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
? "high"
|
|
55
|
-
: allowed.reasoningLevel === "MEDIUM"
|
|
56
|
-
? "medium"
|
|
57
|
-
: allowed.reasoningLevel === "LOW"
|
|
58
|
-
? "low"
|
|
59
|
-
: undefined;
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
model,
|
|
63
|
-
reasoning: reasoningEffort ? { effort: reasoningEffort } : undefined,
|
|
64
|
-
tools: allowed.search ? [{ type: "web_search" as const }] : undefined,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
65
|
+
The same `withMetering` and `withCompression` APIs are exported from
|
|
66
|
+
`@usagetap/sdk/anthropic` and `@usagetap/sdk/openrouter`. Remove the wrapper or
|
|
67
|
+
call `.unwrap()` to recover the original provider client.
|
|
68
|
+
|
|
69
|
+
`withCompression` uses a fast token estimate over the combined request context
|
|
70
|
+
and skips the compression step below 1,000 estimated tokens by default. This
|
|
71
|
+
avoids an extra network round trip when likely savings are small. Override the
|
|
72
|
+
cutoff with `minContextTokens`; set it to `0` to always attempt compression.
|
|
73
|
+
The separate `minTokens` option remains a per-text-segment cutoff.
|
|
67
74
|
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
Wrappers compose. Put metering outside compression so the metered operation
|
|
76
|
+
includes compression and the provider call:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const openai = withMetering(
|
|
80
|
+
withCompression(new OpenAI()),
|
|
81
|
+
"cust_123",
|
|
82
|
+
);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Each `.unwrap()` removes one layer. Do not also set `promptCompression: true` on
|
|
86
|
+
`withMetering` when using a separate `withCompression` layer.
|
|
87
|
+
|
|
88
|
+
For advanced entitlement control, `wrapOpenAI` exposes the full UsageTap context
|
|
89
|
+
and applies entitlement-aware defaults when you omit `model`.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { wrapOpenAI } from "@usagetap/sdk/openai";
|
|
93
|
+
|
|
94
|
+
const ai = wrapOpenAI(openai, usageTap, {
|
|
95
|
+
defaultContext: {
|
|
70
96
|
customerId: "cust_123",
|
|
71
97
|
feature: "chat.send",
|
|
72
98
|
requested: { standard: true, premium: true, search: true, reasoningLevel: "HIGH" },
|
|
73
99
|
},
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
model,
|
|
79
|
-
input: "Draft a welcome email for our Pro plan",
|
|
80
|
-
reasoning,
|
|
81
|
-
tools,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
setUsage({
|
|
85
|
-
modelUsed: model,
|
|
86
|
-
inputTokens: response.usage?.input_tokens ?? response.usage?.prompt_tokens ?? 0,
|
|
87
|
-
cachedInputTokens:
|
|
88
|
-
response.usage?.prompt_tokens_details?.cached_tokens ??
|
|
89
|
-
response.usage?.cache_read_input_tokens ??
|
|
90
|
-
response.usage?.cached_tokens ??
|
|
91
|
-
0,
|
|
92
|
-
responseTokens: response.usage?.output_tokens ?? response.usage?.completion_tokens ?? 0,
|
|
93
|
-
reasoningTokens: reasoning ? response.usage?.reasoning_tokens ?? 0 : 0,
|
|
94
|
-
searches: tools?.length ? response.usage?.web_search_queries ?? 0 : 0,
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
return response;
|
|
100
|
+
promptCompression: {
|
|
101
|
+
provider: "heuristic",
|
|
102
|
+
roles: { user: true, tool: true },
|
|
103
|
+
minTokens: 500,
|
|
98
104
|
},
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
console.log(completion.output_text);
|
|
105
|
+
});
|
|
102
106
|
```
|
|
103
107
|
|
|
104
|
-
|
|
108
|
+
### Prompt compression
|
|
109
|
+
|
|
110
|
+
Prompt compression is an explicit step after `call_begin`. `beginCall` only starts the call and returns the `callId`; `promptCompress` compresses locally, records savings metadata against that call, and returns the compressed prompt for your vendor request. Raw prompt content is not sent to UsageTap.
|
|
105
111
|
|
|
106
112
|
```ts
|
|
113
|
+
import { protectPromptText } from "@usagetap/sdk";
|
|
114
|
+
|
|
115
|
+
const begin = await usageTap.beginCall({
|
|
116
|
+
customerId: "cust_123",
|
|
117
|
+
feature: "chat.send",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const compressed = await usageTap.promptCompress({
|
|
121
|
+
callId: begin.data.callId,
|
|
122
|
+
input: `Please summarize this long prompt but keep ${protectPromptText("PLAN_ID_PRO_2026")} exact.`,
|
|
123
|
+
});
|
|
124
|
+
|
|
107
125
|
const response = await openai.responses.create({
|
|
108
|
-
model: "gpt5",
|
|
109
|
-
|
|
110
|
-
input: "What was a positive news story from today?",
|
|
126
|
+
model: "gpt5-mini",
|
|
127
|
+
input: compressed.compressedInput as string,
|
|
111
128
|
});
|
|
112
129
|
```
|
|
113
130
|
|
|
114
|
-
|
|
131
|
+
The default heuristic is conservative: it normalizes whitespace, preserves fenced code indentation, minifies valid embedded JSON, and converts eligible JSON data blocks to TOON when that is smaller. Pass `provider: "toon"` to force local TOON-style encoding for structured data. Savings include both character counts and approximate token counts using lightweight regex tokenization (`[\p{L}\p{N}]+|[^\s]`), not a model-specific BPE tokenizer. If compression or savings reporting fails, the SDK returns the original input with zero savings so the vendor call can continue.
|
|
132
|
+
|
|
133
|
+
`wrapOpenAI()` and `wrapAnthropic()` can also compress prompts automatically after `call_begin` and before the vendor request. This is opt-in via `promptCompression`; assistant messages are skipped by default so historical assistant turns are not rewritten. Compression telemetry is aggregated once per UsageTap call, and stats are available on `ai.promptCompression.totalTokensSaved`.
|
|
115
134
|
|
|
116
135
|
```ts
|
|
117
|
-
import
|
|
136
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
137
|
+
import { wrapAnthropic } from "@usagetap/sdk/anthropic";
|
|
118
138
|
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
`
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! }),
|
|
166
|
-
usageTap,
|
|
167
|
-
{
|
|
168
|
-
defaultContext: { customerId: "cust_123", feature: "chat.send" },
|
|
169
|
-
promptCompression: { roles: { system: true, user: true, tool: true } },
|
|
170
|
-
},
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
await anthropic.messages.create({
|
|
174
|
-
model: "claude-3-5-haiku-latest",
|
|
175
|
-
max_tokens: 512,
|
|
176
|
-
system: "Long system prompt",
|
|
177
|
-
messages: [{ role: "user", content: "Long user prompt" }],
|
|
178
|
-
});
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
Use `provider: "usagetap"` to compress with UsageTap's hosted endpoints. Manual `promptCompress()` and `compressPromptInput()` use the single-text The Token Company-compatible endpoint at `https://compress.usagetap.com/v1/compress`, where `aggressiveness` is a single number from `0.0` to `1.0`. `wrapOpenAI()` and `wrapAnthropic()` use the message/request endpoint at `https://compress.usagetap.com/v1/messages/compress`, where `aggressiveness` may be a per-role object:
|
|
182
|
-
|
|
183
|
-
```ts
|
|
184
|
-
const result = await usageTap.promptCompress({
|
|
185
|
-
callId: begin.data.callId,
|
|
186
|
-
text: "Your text here",
|
|
187
|
-
provider: "usagetap",
|
|
188
|
-
model: "bear-2",
|
|
189
|
-
aggressiveness: 0.5,
|
|
190
|
-
});
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
```ts
|
|
194
|
-
const ai = wrapOpenAI(openai, usageTap, {
|
|
195
|
-
defaultContext: { customerId: "cust_123", feature: "chat.send" },
|
|
196
|
-
promptCompression: {
|
|
197
|
-
provider: "usagetap",
|
|
198
|
-
aggressiveness: { user: 0.5, system: 0.5, tool: 0.5 },
|
|
199
|
-
},
|
|
200
|
-
});
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
`UsageTapClient` sends your UsageTap API key by default. Override `usageTapCompressionEndpoint` for single-text compression or `usageTapCompressionMessagesEndpoint` for wrapper message compression.
|
|
204
|
-
|
|
205
|
-
When using The Token Company, configure `tokenCompanyApiKey` on `UsageTapClient` and set `provider: "thetokencompany"`. Optional `tokenCompanyModel`, `aggressiveness`, and `tokenCompanyAppId` are supported at the client, manual `promptCompress`, and wrapper levels. Use `protectPromptText()` for text that must be passed through unchanged by compression-compatible providers.
|
|
206
|
-
|
|
207
|
-
For advanced custom flows, `compressPromptInput(input, options?)` returns compression results without recording telemetry, and `recordPromptCompression({ callId, promptCompression })` records precomputed savings metadata against a call.
|
|
208
|
-
|
|
209
|
-
> **Heads up:** `UsageTapClient` always negotiates the canonical UsageTap media type by sending `Accept: application/vnd.usagetap.v1+json`. Every response now uses the `{ result, data, correlationId }` envelope exclusively and the begin payload includes `data.idempotency.key` (always matching `callId`), per-meter snapshots, and subscription metadata. Set `autoIdempotency: false` (or pass your own `idempotency`) to skip the SDK's auto-generated key and rely on the server's deterministic fallback when retriable semantics are acceptable.
|
|
139
|
+
const anthropic = wrapAnthropic(
|
|
140
|
+
new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! }),
|
|
141
|
+
usageTap,
|
|
142
|
+
{
|
|
143
|
+
defaultContext: { customerId: "cust_123", feature: "chat.send" },
|
|
144
|
+
promptCompression: { roles: { system: true, user: true, tool: true } },
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
await anthropic.messages.create({
|
|
149
|
+
model: "claude-3-5-haiku-latest",
|
|
150
|
+
max_tokens: 512,
|
|
151
|
+
system: "Long system prompt",
|
|
152
|
+
messages: [{ role: "user", content: "Long user prompt" }],
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Use `provider: "usagetap"` to compress with UsageTap's hosted endpoints. Manual `promptCompress()` and `compressPromptInput()` use the single-text The Token Company-compatible endpoint at `https://compress.usagetap.com/v1/compress`, where `aggressiveness` is a single number from `0.0` to `1.0`. `wrapOpenAI()` and `wrapAnthropic()` use the message/request endpoint at `https://compress.usagetap.com/v1/messages/compress`, where `aggressiveness` may be a per-role object:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const result = await usageTap.promptCompress({
|
|
160
|
+
callId: begin.data.callId,
|
|
161
|
+
text: "Your text here",
|
|
162
|
+
provider: "usagetap",
|
|
163
|
+
model: "bear-2",
|
|
164
|
+
aggressiveness: 0.5,
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
const ai = wrapOpenAI(openai, usageTap, {
|
|
170
|
+
defaultContext: { customerId: "cust_123", feature: "chat.send" },
|
|
171
|
+
promptCompression: {
|
|
172
|
+
provider: "usagetap",
|
|
173
|
+
aggressiveness: { user: 0.5, system: 0.5, tool: 0.5 },
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`UsageTapClient` sends your UsageTap API key by default. Override `usageTapCompressionEndpoint` for single-text compression or `usageTapCompressionMessagesEndpoint` for wrapper message compression.
|
|
179
|
+
|
|
180
|
+
When using The Token Company, configure `tokenCompanyApiKey` on `UsageTapClient` and set `provider: "thetokencompany"`. Optional `tokenCompanyModel`, `aggressiveness`, and `tokenCompanyAppId` are supported at the client, manual `promptCompress`, and wrapper levels. Use `protectPromptText()` for text that must be passed through unchanged by compression-compatible providers.
|
|
181
|
+
|
|
182
|
+
For advanced custom flows, `compressPromptInput(input, options?)` returns compression results without recording telemetry, and `recordPromptCompression({ callId, promptCompression })` records precomputed savings metadata against a call.
|
|
183
|
+
|
|
184
|
+
> **Heads up:** `UsageTapClient` always negotiates the canonical UsageTap media type by sending `Accept: application/vnd.usagetap.v1+json`. Every response now uses the `{ result, data, correlationId }` envelope exclusively and the begin payload includes `data.idempotency.key` (always matching `callId`), per-meter snapshots, and subscription metadata. Set `autoIdempotency: false` (or pass your own `idempotency`) to skip the SDK's auto-generated key and rely on the server's deterministic fallback when retriable semantics are acceptable.
|
|
210
185
|
|
|
211
186
|
### Streaming helpers
|
|
212
187
|
|
|
@@ -496,19 +471,19 @@ Key exports from `@usagetap/sdk`:
|
|
|
496
471
|
- `createCustomer` – idempotently ensure a customer subscription exists before starting a call.
|
|
497
472
|
- `changePlan` – switch a customer to a different usage plan with configurable strategy (immediate reset, prorated, or scheduled).
|
|
498
473
|
- `incrementCustomMeter` – track custom usage metrics beyond standard LLM counters (agent actions, documents, API calls, etc.).
|
|
499
|
-
- `checkUsage` – lightweight method to query current usage status without creating a call session.
|
|
500
|
-
- `promptCompress` / `compressPromptToon` – compress prompt input after `call_begin`, return the compressed payload, and record savings metadata for the call.
|
|
501
|
-
- `protectPromptText` / `protect` – mark exact text spans that compatible compressors should not rewrite.
|
|
502
|
-
- `wrapFetch` – wraps a fetch function to automatically instrument OpenAI API calls (minimal integration).
|
|
474
|
+
- `checkUsage` – lightweight method to query current usage status without creating a call session.
|
|
475
|
+
- `promptCompress` / `compressPromptToon` – compress prompt input after `call_begin`, return the compressed payload, and record savings metadata for the call.
|
|
476
|
+
- `protectPromptText` / `protect` – mark exact text spans that compatible compressors should not rewrite.
|
|
477
|
+
- `wrapFetch` – wraps a fetch function to automatically instrument OpenAI API calls (minimal integration).
|
|
503
478
|
- `createIdempotencyKey` – helper for generating UsageTap-compatible idempotency keys.
|
|
504
479
|
- Type definitions for canonical UsageTap request/response payloads.
|
|
505
480
|
|
|
506
|
-
Optional subpaths:
|
|
507
|
-
|
|
508
|
-
- `@usagetap/sdk/openai` – `wrapOpenAI`, `createOpenAIAdapter`, `streamOpenAIRoute`, `toNextResponse`, `pipeToResponse`, and related types.
|
|
509
|
-
- `@usagetap/sdk/anthropic` – `wrapAnthropic` and related prompt compression types.
|
|
510
|
-
- `@usagetap/sdk/express` – `withUsage`, `withUsageMiddleware`, and corresponding Express request types.
|
|
511
|
-
- `@usagetap/sdk/react` – `useChatWithUsage` and supporting types for building chat interfaces.
|
|
481
|
+
Optional subpaths:
|
|
482
|
+
|
|
483
|
+
- `@usagetap/sdk/openai` – `wrapOpenAI`, `createOpenAIAdapter`, `streamOpenAIRoute`, `toNextResponse`, `pipeToResponse`, and related types.
|
|
484
|
+
- `@usagetap/sdk/anthropic` – `wrapAnthropic` and related prompt compression types.
|
|
485
|
+
- `@usagetap/sdk/express` – `withUsage`, `withUsageMiddleware`, and corresponding Express request types.
|
|
486
|
+
- `@usagetap/sdk/react` – `useChatWithUsage` and supporting types for building chat interfaces.
|
|
512
487
|
|
|
513
488
|
All helpers are designed for server runtimes. Use `UsageTapClient` with `allowBrowser: true` only for sandbox/test scenarios.
|
|
514
489
|
|
|
@@ -669,18 +644,18 @@ UsageTap responds exclusively with the canonical `{ result, data, correlationId
|
|
|
669
644
|
"search": true,
|
|
670
645
|
"reasoningLevel": "MEDIUM"
|
|
671
646
|
},
|
|
672
|
-
"entitlementHints": {
|
|
673
|
-
"suggestedModelTier": "standard",
|
|
674
|
-
"reasoningLevel": "MEDIUM",
|
|
675
|
-
"policy": "DOWNGRADE",
|
|
676
|
-
"downgrade": {
|
|
677
|
-
"reason": "PREMIUM_QUOTA_EXHAUSTED",
|
|
678
|
-
"fallbackTier": "standard"
|
|
679
|
-
}
|
|
680
|
-
},
|
|
681
|
-
"meters": {
|
|
682
|
-
"standardCalls": {
|
|
683
|
-
"remaining": 12,
|
|
647
|
+
"entitlementHints": {
|
|
648
|
+
"suggestedModelTier": "standard",
|
|
649
|
+
"reasoningLevel": "MEDIUM",
|
|
650
|
+
"policy": "DOWNGRADE",
|
|
651
|
+
"downgrade": {
|
|
652
|
+
"reason": "PREMIUM_QUOTA_EXHAUSTED",
|
|
653
|
+
"fallbackTier": "standard"
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
"meters": {
|
|
657
|
+
"standardCalls": {
|
|
658
|
+
"remaining": 12,
|
|
684
659
|
"limit": 20,
|
|
685
660
|
"used": 8,
|
|
686
661
|
"unlimited": false,
|
|
@@ -731,8 +706,8 @@ UsageTap responds exclusively with the canonical `{ result, data, correlationId
|
|
|
731
706
|
|
|
732
707
|
`UsageTapClient` exposes the normalized structure via `UsageTapSuccessResponse<BeginCallResponseBody>`. In addition to the flattened `allowed` map, the begin response now ships richer metadata:
|
|
733
708
|
|
|
734
|
-
- `entitlementHints` summarises the recommended model tier and downgrade rationale based on the active policy.
|
|
735
|
-
- `meters` is a per-counter snapshot including remaining quotas, total limits, usage to date, and convenience ratios. `remainingRatios` mirrors the same information in a compact map for quick lookups.
|
|
709
|
+
- `entitlementHints` summarises the recommended model tier and downgrade rationale based on the active policy.
|
|
710
|
+
- `meters` is a per-counter snapshot including remaining quotas, total limits, usage to date, and convenience ratios. `remainingRatios` mirrors the same information in a compact map for quick lookups.
|
|
736
711
|
- `subscription` contains the active plan identity, versioning, and upcoming replenishment timestamps so you can render customer-facing UI without querying Dynamo yourself.
|
|
737
712
|
- `models` surfaces per-organization vendor hints (e.g. standard vs. premium model shortlists).
|
|
738
713
|
- `idempotency` reveals the actual key that was persisted (`callId` mirrors this value). When you omit `idempotency` in the request, the backend derives a deterministic hash from organization, customer, feature, and requested entitlements.
|
|
@@ -757,48 +732,48 @@ UsageTap responds exclusively with the canonical `{ result, data, correlationId
|
|
|
757
732
|
"responseTokens": 288,
|
|
758
733
|
"reasoningTokens": 0
|
|
759
734
|
},
|
|
760
|
-
"metered": {
|
|
761
|
-
"tokens": 768,
|
|
762
|
-
"calls": 1,
|
|
763
|
-
"searches": 1
|
|
764
|
-
},
|
|
765
|
-
"spendVelocity": {
|
|
766
|
-
"currency": "USD",
|
|
767
|
-
"source": "usage_aggregate",
|
|
768
|
-
"generatedAt": "2025-10-04T18:21:52.103Z",
|
|
769
|
-
"customerId": "cust_123",
|
|
770
|
-
"currentCallCostUsd": 0,
|
|
771
|
-
"windows": {
|
|
772
|
-
"hour": {
|
|
773
|
-
"bucket": "2025-10-04T18",
|
|
774
|
-
"windowMinutes": 60,
|
|
775
|
-
"startedAt": "2025-10-04T18:00:00.000Z",
|
|
776
|
-
"endedAt": "2025-10-04T18:21:52.103Z",
|
|
777
|
-
"completedCostUsd": 8.75,
|
|
778
|
-
"completedCalls": 24
|
|
779
|
-
},
|
|
780
|
-
"day": {
|
|
781
|
-
"bucket": "2025-10-04",
|
|
782
|
-
"windowMinutes": 1440,
|
|
783
|
-
"startedAt": "2025-10-04T00:00:00.000Z",
|
|
784
|
-
"endedAt": "2025-10-04T18:21:52.103Z",
|
|
785
|
-
"completedCostUsd": 42.1,
|
|
786
|
-
"completedCalls": 140
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
},
|
|
791
|
-
"correlationId": "corr_abc123"
|
|
792
|
-
}
|
|
735
|
+
"metered": {
|
|
736
|
+
"tokens": 768,
|
|
737
|
+
"calls": 1,
|
|
738
|
+
"searches": 1
|
|
739
|
+
},
|
|
740
|
+
"spendVelocity": {
|
|
741
|
+
"currency": "USD",
|
|
742
|
+
"source": "usage_aggregate",
|
|
743
|
+
"generatedAt": "2025-10-04T18:21:52.103Z",
|
|
744
|
+
"customerId": "cust_123",
|
|
745
|
+
"currentCallCostUsd": 0,
|
|
746
|
+
"windows": {
|
|
747
|
+
"hour": {
|
|
748
|
+
"bucket": "2025-10-04T18",
|
|
749
|
+
"windowMinutes": 60,
|
|
750
|
+
"startedAt": "2025-10-04T18:00:00.000Z",
|
|
751
|
+
"endedAt": "2025-10-04T18:21:52.103Z",
|
|
752
|
+
"completedCostUsd": 8.75,
|
|
753
|
+
"completedCalls": 24
|
|
754
|
+
},
|
|
755
|
+
"day": {
|
|
756
|
+
"bucket": "2025-10-04",
|
|
757
|
+
"windowMinutes": 1440,
|
|
758
|
+
"startedAt": "2025-10-04T00:00:00.000Z",
|
|
759
|
+
"endedAt": "2025-10-04T18:21:52.103Z",
|
|
760
|
+
"completedCostUsd": 42.1,
|
|
761
|
+
"completedCalls": 140
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
"correlationId": "corr_abc123"
|
|
767
|
+
}
|
|
793
768
|
```
|
|
794
769
|
|
|
795
770
|
Send `cachedInputTokens` when available so UsageTap can apply provider cache-read pricing correctly.
|
|
796
771
|
|
|
797
|
-
`metered` is derived from the raw Dynamo deltas. Additional meters (audio seconds, reasoning tokens, balances) will populate in later phases without breaking the contract.
|
|
798
|
-
|
|
799
|
-
`spendVelocity` is aggregate-backed current UTC hour/day telemetry. UsageTap does not enforce limits from this section; `currentCallCostUsd` is included separately because aggregate updates are asynchronous.
|
|
800
|
-
|
|
801
|
-
### Premium detection and override
|
|
772
|
+
`metered` is derived from the raw Dynamo deltas. Additional meters (audio seconds, reasoning tokens, balances) will populate in later phases without breaking the contract.
|
|
773
|
+
|
|
774
|
+
`spendVelocity` is aggregate-backed current UTC hour/day telemetry. UsageTap does not enforce limits from this section; `currentCallCostUsd` is included separately because aggregate updates are asynchronous.
|
|
775
|
+
|
|
776
|
+
### Premium detection and override
|
|
802
777
|
|
|
803
778
|
UsageTap automatically determines whether a call is premium based on the model's output token pricing:
|
|
804
779
|
- If the output token price exceeds **$4.00 per million tokens**, the call is classified as premium
|