@tollgateai/sdk 0.2.1 → 0.4.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/README.md +286 -111
- package/dist/index.cjs +26 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +26 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,72 +1,106 @@
|
|
|
1
1
|
# @tollgateai/sdk
|
|
2
2
|
|
|
3
|
-
Track
|
|
4
|
-
[Tollgate](https://tollgateai.vercel.app). The SDK reads the actual `usage`
|
|
5
|
-
object off each provider response — you never hand-count tokens.
|
|
3
|
+
> Real-time gross-margin observability for AI agents. Track every LLM call's cost, attribute it to a customer, and see whether you're making money — before the invoice goes out.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
**v0.4.0** · [npm](https://www.npmjs.com/package/@tollgateai/sdk) · [Dashboard](https://tollgateai.vercel.app)
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Why Tollgate
|
|
10
|
+
|
|
11
|
+
You sell an AI-powered product. Each customer interaction triggers LLM calls that cost you real money — input tokens, output tokens, reasoning tokens, cached tokens, tool calls. Tollgate captures that cost automatically from provider responses, joins it with the revenue your pricing model defines, and shows you per-customer, per-agent, per-run gross margin in real time.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
13
14
|
|
|
14
15
|
```bash
|
|
15
16
|
npm install @tollgateai/sdk
|
|
16
|
-
# or: pnpm add @tollgateai/sdk / yarn add @tollgateai/sdk
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Create an API key in **Tollgate → Integrations**, then set:
|
|
20
|
-
|
|
21
19
|
```bash
|
|
22
|
-
|
|
23
|
-
# optional, defaults to the hosted app:
|
|
24
|
-
TOLLGATE_BASE_URL=https://tollgateai.vercel.app
|
|
20
|
+
pnpm add @tollgateai/sdk # or yarn add @tollgateai/sdk
|
|
25
21
|
```
|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
Requires Node.js 18+. Zero runtime dependencies.
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
### Anthropic
|
|
25
|
+
## Quick Start
|
|
32
26
|
|
|
33
27
|
```ts
|
|
34
28
|
import Anthropic from '@anthropic-ai/sdk';
|
|
35
29
|
import { createTollgateClient, wrapAnthropic } from '@tollgateai/sdk';
|
|
36
30
|
|
|
37
|
-
const tollgate = createTollgateClient();
|
|
38
|
-
|
|
39
|
-
// Pin a runId so every call in this run is grouped and reports cost only.
|
|
40
|
-
const runId = 'ticket_8842';
|
|
31
|
+
const tollgate = createTollgateClient(); // reads TOLLGATE_API_KEY from env
|
|
41
32
|
const anthropic = wrapAnthropic(new Anthropic(), tollgate, {
|
|
42
|
-
customerId: '
|
|
43
|
-
runId,
|
|
33
|
+
customerId: 'cust_acme',
|
|
34
|
+
runId: 'ticket_8842',
|
|
44
35
|
});
|
|
45
36
|
|
|
46
|
-
//
|
|
47
|
-
await anthropic.messages.create({
|
|
37
|
+
// Every call is tracked automatically — tokens, cost, tool calls.
|
|
38
|
+
const msg = await anthropic.messages.create({
|
|
48
39
|
model: 'claude-sonnet-4-6',
|
|
49
|
-
max_tokens:
|
|
50
|
-
messages: [{ role: 'user', content: 'Resolve this
|
|
40
|
+
max_tokens: 1024,
|
|
41
|
+
messages: [{ role: 'user', content: 'Resolve this billing dispute…' }],
|
|
51
42
|
});
|
|
52
43
|
|
|
53
|
-
//
|
|
44
|
+
// Close the run and book revenue.
|
|
54
45
|
await tollgate.resolve({
|
|
55
|
-
runId,
|
|
56
|
-
customerId: '
|
|
57
|
-
outcome: 'resolved',
|
|
58
|
-
revenueUnitCents: 50,
|
|
46
|
+
runId: 'ticket_8842',
|
|
47
|
+
customerId: 'cust_acme',
|
|
48
|
+
outcome: 'resolved',
|
|
49
|
+
revenueUnitCents: 50, // $0.50 per resolved ticket
|
|
59
50
|
});
|
|
60
51
|
```
|
|
61
52
|
|
|
62
|
-
|
|
53
|
+
## Provider Support
|
|
54
|
+
|
|
55
|
+
| Provider | Wrapper | Streaming | Tool-Call Tracking |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| Anthropic | `wrapAnthropic` | Automatic | Counts `tool_use` content blocks |
|
|
58
|
+
| OpenAI | `wrapOpenAI` | Needs `stream_options: { include_usage: true }` | Counts `tool_calls` on choices |
|
|
59
|
+
| OpenAI-compatible (Groq, OpenRouter, Together, Nebius, vLLM, …) | `wrapOpenAI` with `provider: 'openai_compatible'` | Same as OpenAI | Same as OpenAI |
|
|
60
|
+
| AWS Bedrock | `wrapBedrock` | Automatic | Counts `toolUse` content blocks |
|
|
61
|
+
|
|
62
|
+
## Configuration
|
|
63
|
+
|
|
64
|
+
| Environment Variable | Required | Default |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `TOLLGATE_API_KEY` | Yes | — |
|
|
67
|
+
| `TOLLGATE_BASE_URL` | No | `https://tollgateai.vercel.app` |
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
Or pass them directly:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const tollgate = createTollgateClient({
|
|
73
|
+
apiKey: 'tg_live_xxx',
|
|
74
|
+
baseUrl: 'https://tollgateai.vercel.app',
|
|
75
|
+
timeoutMs: 10_000, // per-request timeout (default 10s)
|
|
76
|
+
maxRetries: 2, // retries on 5xx/429/network (default 2)
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Auto-Instrumentation
|
|
83
|
+
|
|
84
|
+
Wrap your provider client once. Every `create` call reports usage in the background — non-blocking, fire-and-forget. Failures go to `onError` (default: `console.warn`) and never break your LLM call.
|
|
85
|
+
|
|
86
|
+
### Anthropic
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
90
|
+
import { createTollgateClient, wrapAnthropic } from '@tollgateai/sdk';
|
|
91
|
+
|
|
92
|
+
const tollgate = createTollgateClient();
|
|
93
|
+
const anthropic = wrapAnthropic(new Anthropic(), tollgate, {
|
|
94
|
+
customerId: 'cust_acme',
|
|
95
|
+
runId: 'ticket_8842',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await anthropic.messages.create({
|
|
99
|
+
model: 'claude-sonnet-4-6',
|
|
100
|
+
max_tokens: 512,
|
|
101
|
+
messages: [{ role: 'user', content: 'Summarize this ticket…' }],
|
|
102
|
+
});
|
|
103
|
+
```
|
|
70
104
|
|
|
71
105
|
### OpenAI
|
|
72
106
|
|
|
@@ -75,7 +109,7 @@ import OpenAI from 'openai';
|
|
|
75
109
|
import { createTollgateClient, wrapOpenAI } from '@tollgateai/sdk';
|
|
76
110
|
|
|
77
111
|
const tollgate = createTollgateClient();
|
|
78
|
-
const openai = wrapOpenAI(new OpenAI(), tollgate, { customerId: '
|
|
112
|
+
const openai = wrapOpenAI(new OpenAI(), tollgate, { customerId: 'cust_acme' });
|
|
79
113
|
|
|
80
114
|
await openai.chat.completions.create({
|
|
81
115
|
model: 'gpt-4o',
|
|
@@ -83,82 +117,175 @@ await openai.chat.completions.create({
|
|
|
83
117
|
});
|
|
84
118
|
```
|
|
85
119
|
|
|
86
|
-
|
|
87
|
-
`revenueUnitCents: (res) => res.someField ? 50 : 0`.
|
|
120
|
+
### OpenAI-Compatible Gateways
|
|
88
121
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
Point the OpenAI SDK at any compatible endpoint and set `provider:
|
|
92
|
-
'openai_compatible'` so the server prices it from the gateway-echoed model name:
|
|
122
|
+
Point the OpenAI SDK at any compatible endpoint and set `provider: 'openai_compatible'`:
|
|
93
123
|
|
|
94
124
|
```ts
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
125
|
+
import OpenAI from 'openai';
|
|
126
|
+
import { createTollgateClient, wrapOpenAI } from '@tollgateai/sdk';
|
|
127
|
+
|
|
128
|
+
const tollgate = createTollgateClient();
|
|
129
|
+
const groq = wrapOpenAI(
|
|
130
|
+
new OpenAI({ apiKey: process.env.GROQ_API_KEY, baseURL: 'https://api.groq.com/openai/v1' }),
|
|
131
|
+
tollgate,
|
|
132
|
+
{ customerId: 'cust_acme', provider: 'openai_compatible' },
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
await groq.chat.completions.create({
|
|
136
|
+
model: 'llama-3.3-70b-versatile',
|
|
137
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
99
138
|
});
|
|
100
|
-
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### AWS Bedrock
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { BedrockRuntimeClient, ConverseCommand } from '@aws-sdk/client-bedrock-runtime';
|
|
145
|
+
import { createTollgateClient, wrapBedrock } from '@tollgateai/sdk';
|
|
146
|
+
|
|
147
|
+
const tollgate = createTollgateClient();
|
|
148
|
+
const bedrock = wrapBedrock(
|
|
149
|
+
new BedrockRuntimeClient({ region: 'us-east-1' }),
|
|
150
|
+
tollgate,
|
|
151
|
+
{ customerId: 'cust_acme' },
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
await bedrock.send(new ConverseCommand({
|
|
155
|
+
modelId: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
|
|
156
|
+
messages: [{ role: 'user', content: [{ text: 'Hello' }] }],
|
|
157
|
+
}));
|
|
101
158
|
```
|
|
102
159
|
|
|
103
160
|
### Streaming
|
|
104
161
|
|
|
105
|
-
Streaming is captured automatically
|
|
106
|
-
|
|
107
|
-
**
|
|
162
|
+
Streaming is captured automatically — iterate the stream as usual and usage is reported when the stream ends.
|
|
163
|
+
|
|
164
|
+
**OpenAI / compatible** requires `stream_options: { include_usage: true }` for the final usage chunk. **Anthropic** and **Bedrock** need no extra flags.
|
|
108
165
|
|
|
109
166
|
```ts
|
|
110
|
-
const stream = await
|
|
111
|
-
model: 'gpt-4o',
|
|
167
|
+
const stream = await openai.chat.completions.create({
|
|
168
|
+
model: 'gpt-4o',
|
|
169
|
+
stream: true,
|
|
170
|
+
stream_options: { include_usage: true },
|
|
112
171
|
messages: [{ role: 'user', content: 'Hello' }],
|
|
113
172
|
});
|
|
114
|
-
for await (const chunk of stream) { /*
|
|
173
|
+
for await (const chunk of stream) { /* render to UI */ }
|
|
174
|
+
// Usage reported automatically when stream ends.
|
|
115
175
|
```
|
|
116
176
|
|
|
117
|
-
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## What Gets Tracked
|
|
180
|
+
|
|
181
|
+
Every auto-instrumented call captures the following from the provider response:
|
|
118
182
|
|
|
119
|
-
|
|
120
|
-
|
|
183
|
+
| Field | Source | Description |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| `tokensIn` | `usage.input_tokens` / `prompt_tokens` | Input tokens consumed |
|
|
186
|
+
| `tokensOut` | `usage.output_tokens` / `completion_tokens` | Output tokens generated |
|
|
187
|
+
| `reasoningTokens` | `completion_tokens_details.reasoning_tokens` | Reasoning/chain-of-thought tokens (OpenAI) |
|
|
188
|
+
| `cachedTokens` | `cache_read_input_tokens` / `cached_tokens` | Prompt cache read tokens |
|
|
189
|
+
| `cacheWrite5mTokens` | `cache_creation_input_tokens` | 5-min TTL cache write tokens |
|
|
190
|
+
| `cacheWrite1hTokens` | `cache_creation.ephemeral_1h_input_tokens` | 1-hour TTL cache write tokens |
|
|
191
|
+
| `toolCalls` | Content block / choice inspection | Number of tool calls in the response |
|
|
192
|
+
| `externalCostCents` | User-provided | Cost of external tools/services (image gen, sandbox, search) |
|
|
193
|
+
| `provider` | Wrapper default or override | `anthropic`, `openai`, `openai_compatible`, `bedrock` |
|
|
194
|
+
| `model` | Response object | Model identifier as reported by the provider |
|
|
195
|
+
|
|
196
|
+
Cost is computed **server-side** from token counts and a rate card that auto-syncs daily from the public LiteLLM registry. Unknown models are priced at $0 and flagged in logs.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Outcome-Based Pricing
|
|
201
|
+
|
|
202
|
+
Under per-resolution pricing, only a **resolved** run earns revenue. An escalated or failed run earns $0 but its provider cost still counts. The pattern:
|
|
203
|
+
|
|
204
|
+
1. **Wrap** to meter cost on every LLM call (automatic).
|
|
205
|
+
2. **Resolve** once at the end to book the outcome.
|
|
121
206
|
|
|
122
207
|
```ts
|
|
123
|
-
|
|
124
|
-
|
|
208
|
+
const runId = 'ticket_8842';
|
|
209
|
+
const anthropic = wrapAnthropic(new Anthropic(), tollgate, {
|
|
210
|
+
customerId: 'cust_acme',
|
|
211
|
+
runId,
|
|
212
|
+
});
|
|
125
213
|
|
|
126
|
-
|
|
127
|
-
|
|
214
|
+
// … multiple LLM calls within this run …
|
|
215
|
+
|
|
216
|
+
await tollgate.resolve({
|
|
217
|
+
runId,
|
|
218
|
+
customerId: 'cust_acme',
|
|
219
|
+
outcome: 'resolved', // 'resolved' | 'escalated' | 'failed'
|
|
220
|
+
revenueUnitCents: 50,
|
|
221
|
+
});
|
|
128
222
|
```
|
|
129
223
|
|
|
130
|
-
|
|
224
|
+
For simple per-call billing, pass `revenueUnitCents` in the wrap options and skip `resolve()`.
|
|
131
225
|
|
|
132
|
-
|
|
133
|
-
uses it verbatim, skipping the rate card entirely.
|
|
226
|
+
---
|
|
134
227
|
|
|
135
|
-
##
|
|
228
|
+
## External Tool Costs
|
|
136
229
|
|
|
137
|
-
|
|
138
|
-
revenue (especially `usage_based`, which is computed at ingest) is recognized from
|
|
139
|
-
the first event. Idempotent — safe to run on every boot.
|
|
230
|
+
AI agents often call external services — image generation, code sandboxes, search APIs, vector databases — that cost real money outside of LLM token pricing. Report these costs alongside the LLM call so Tollgate includes them in the margin calculation.
|
|
140
231
|
|
|
141
232
|
```ts
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
233
|
+
// Agent generates an image during the run
|
|
234
|
+
const image = await dalle.generate({ prompt: '...' }); // costs $0.04
|
|
235
|
+
|
|
236
|
+
await tollgate.track({
|
|
237
|
+
customerId: 'cust_acme',
|
|
238
|
+
runId: 'ticket_8842',
|
|
239
|
+
provider: 'openai',
|
|
240
|
+
model: 'gpt-4o',
|
|
241
|
+
tokensIn: 500,
|
|
242
|
+
tokensOut: 200,
|
|
243
|
+
toolCalls: 1, // LLM tool_use count (auto-extracted by wrappers)
|
|
244
|
+
externalCostCents: 4.0, // $0.04 for the DALL-E call
|
|
245
|
+
idempotencyKey: 'ticket_8842#step_2',
|
|
148
246
|
});
|
|
149
247
|
```
|
|
150
248
|
|
|
151
|
-
|
|
249
|
+
Common examples:
|
|
250
|
+
|
|
251
|
+
| Service | Typical Cost | How to report |
|
|
252
|
+
|---|---|---|
|
|
253
|
+
| DALL-E image generation | ~$0.04/image | `externalCostCents: 4` |
|
|
254
|
+
| E2B code sandbox | ~$0.01/run | `externalCostCents: 1` |
|
|
255
|
+
| Tavily search API | ~$0.01/search | `externalCostCents: 1` |
|
|
256
|
+
| Pinecone vector query | ~$0.001/query | `externalCostCents: 0.1` |
|
|
152
257
|
|
|
153
|
-
|
|
258
|
+
External costs flow into the **Tools** bucket in the Customer Drawer's cost-split chart — so you can see exactly what share of each customer's cost comes from external services vs. LLM tokens.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Customer & Plan Setup
|
|
263
|
+
|
|
264
|
+
Create customers and assign plans **before** sending usage so plan-priced revenue is recognized from the first event. Idempotent — safe to run on every boot.
|
|
154
265
|
|
|
155
266
|
```ts
|
|
156
|
-
|
|
267
|
+
await tollgate.upsertCustomer({
|
|
268
|
+
customerId: 'cust_acme',
|
|
269
|
+
name: 'Acme Corp',
|
|
270
|
+
company: 'Acme Corp',
|
|
271
|
+
seats: 5,
|
|
272
|
+
plan: {
|
|
273
|
+
name: 'Pro Plan',
|
|
274
|
+
pricingModel: 'usage_based', // per_unit | per_resolution | usage_based | per_seat | flat | hybrid
|
|
275
|
+
unitRevenueCents: 10,
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
```
|
|
157
279
|
|
|
158
|
-
|
|
280
|
+
---
|
|
159
281
|
|
|
282
|
+
## Manual Tracking
|
|
283
|
+
|
|
284
|
+
For full control, unusual providers, or non-LLM cost events:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
160
287
|
await tollgate.track({
|
|
161
|
-
customerId: '
|
|
288
|
+
customerId: 'cust_acme',
|
|
162
289
|
runId: 'run_12345',
|
|
163
290
|
provider: 'anthropic',
|
|
164
291
|
model: 'claude-sonnet-4-6',
|
|
@@ -166,35 +293,83 @@ await tollgate.track({
|
|
|
166
293
|
tokensOut: 450,
|
|
167
294
|
reasoningTokens: 0,
|
|
168
295
|
cachedTokens: 0,
|
|
296
|
+
toolCalls: 2,
|
|
169
297
|
revenueUnitCents: 50,
|
|
170
|
-
idempotencyKey: 'run_12345#step_1',
|
|
298
|
+
idempotencyKey: 'run_12345#step_1',
|
|
299
|
+
});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Already have an exact cost?
|
|
303
|
+
|
|
304
|
+
Pass `providerCostCents` (a number or a function of the response) and the server uses it verbatim, skipping the rate card entirely:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
const anthropic = wrapAnthropic(new Anthropic(), tollgate, {
|
|
308
|
+
customerId: 'cust_acme',
|
|
309
|
+
providerCostCents: 3.5, // or: (response) => computeMyOwnCost(response)
|
|
171
310
|
});
|
|
172
311
|
```
|
|
173
312
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## API Reference
|
|
316
|
+
|
|
317
|
+
### Exports
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
// Client
|
|
321
|
+
createTollgateClient(options?) // → TollgateClient
|
|
322
|
+
TollgateError // Custom error with status & body
|
|
323
|
+
|
|
324
|
+
// Auto-instrumentation wrappers
|
|
325
|
+
wrapAnthropic(client, tollgate, options) // → instrumented Anthropic client
|
|
326
|
+
wrapOpenAI(client, tollgate, options) // → instrumented OpenAI / compatible client
|
|
327
|
+
wrapBedrock(client, tollgate, options) // → instrumented Bedrock Runtime client
|
|
328
|
+
|
|
329
|
+
// Low-level event builders (for manual track payloads)
|
|
330
|
+
anthropicEventFrom(msg, options) // → TrackEventInput | null
|
|
331
|
+
openAIEventFrom(completion, options) // → TrackEventInput | null
|
|
332
|
+
bedrockEventFrom(usage, model, options) // → TrackEventInput | null
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### TollgateClient
|
|
336
|
+
|
|
337
|
+
| Method | Description |
|
|
338
|
+
|---|---|
|
|
339
|
+
| `track(event)` | Report a single usage event. Idempotent on `idempotencyKey`. |
|
|
340
|
+
| `resolve(input)` | Close a run with an outcome. Books revenue only when `outcome` is `'resolved'`. |
|
|
341
|
+
| `upsertCustomer(input)` | Create or update a customer and optionally assign a plan. |
|
|
342
|
+
|
|
343
|
+
### InstrumentOptions
|
|
344
|
+
|
|
345
|
+
| Field | Type | Required | Description |
|
|
346
|
+
|---|---|---|---|
|
|
347
|
+
| `customerId` | `string` | Yes | Your end customer's stable identifier. |
|
|
348
|
+
| `agentId` | `string` | No | Agent or workflow identifier. |
|
|
349
|
+
| `runId` | `string \| () => string` | No | Logical run ID. Defaults to the provider response ID. |
|
|
350
|
+
| `provider` | `Provider` | No | Override the reported provider (e.g. `'openai_compatible'`). |
|
|
351
|
+
| `revenueUnitCents` | `number \| (response) => number` | No | Revenue per call in cents. |
|
|
352
|
+
| `providerCostCents` | `number \| (response) => number` | No | Exact cost override — skips rate card. |
|
|
353
|
+
| `onError` | `(err) => void` | No | Error handler for background tracking (default: `console.warn`). |
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## How It Works
|
|
358
|
+
|
|
359
|
+
1. **Proxy wrappers** intercept `messages.create` / `chat.completions.create` / `send` without modifying the request or response.
|
|
360
|
+
2. After the provider responds, the wrapper extracts token counts, tool call counts, and metadata from the response's `usage` object and content blocks.
|
|
361
|
+
3. A `POST /api/track` is fired **in the background** — non-blocking, with automatic retries on transient failures.
|
|
362
|
+
4. The server computes cost from tokens via rate cards, joins it with your plan-configured revenue, and updates real-time margin rollups.
|
|
363
|
+
5. Events are **idempotent** on `idempotencyKey` (auto-set to the provider response ID), so retries and stream replays never double-count.
|
|
364
|
+
|
|
365
|
+
## Privacy & Security
|
|
366
|
+
|
|
367
|
+
- **No prompt content is ever sent.** Only token counts, model name, and metadata.
|
|
368
|
+
- Events are deduplicated server-side — safe to retry.
|
|
369
|
+
- Background tracking never throws into your application code.
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## License
|
|
374
|
+
|
|
375
|
+
Licensed for use with Tollgate.
|
package/dist/index.cjs
CHANGED
|
@@ -161,6 +161,7 @@ function anthropicEventFrom(msg, opts) {
|
|
|
161
161
|
const fivem = usage.cache_creation?.ephemeral_5m_input_tokens;
|
|
162
162
|
const oneh = usage.cache_creation?.ephemeral_1h_input_tokens;
|
|
163
163
|
const hasSplit = fivem !== void 0 || oneh !== void 0;
|
|
164
|
+
const toolCalls = Array.isArray(msg.content) ? msg.content.filter((b) => b.type === "tool_use").length : 0;
|
|
164
165
|
const event = {
|
|
165
166
|
customerId: opts.customerId,
|
|
166
167
|
agentId: opts.agentId,
|
|
@@ -172,6 +173,7 @@ function anthropicEventFrom(msg, opts) {
|
|
|
172
173
|
cachedTokens: usage.cache_read_input_tokens ?? 0,
|
|
173
174
|
cacheWrite5mTokens: hasSplit ? fivem ?? 0 : usage.cache_creation_input_tokens ?? 0,
|
|
174
175
|
cacheWrite1hTokens: hasSplit ? oneh ?? 0 : 0,
|
|
176
|
+
toolCalls,
|
|
175
177
|
revenueUnitCents: resolveRevenue(opts, msg),
|
|
176
178
|
idempotencyKey: msg.id ?? `${runId}#${randomId()}`
|
|
177
179
|
};
|
|
@@ -184,6 +186,7 @@ function wrapAnthropic(client, tollgate, opts) {
|
|
|
184
186
|
const result = await original(...args);
|
|
185
187
|
if (isAsyncIterable(result)) {
|
|
186
188
|
const msg = {};
|
|
189
|
+
const toolUseBlocks = [];
|
|
187
190
|
return instrumentStream(
|
|
188
191
|
result,
|
|
189
192
|
(ev) => {
|
|
@@ -193,9 +196,12 @@ function wrapAnthropic(client, tollgate, opts) {
|
|
|
193
196
|
msg.usage = { ...ev.message.usage };
|
|
194
197
|
} else if (ev.type === "message_delta" && ev.usage) {
|
|
195
198
|
msg.usage = { ...msg.usage ?? {}, output_tokens: ev.usage.output_tokens };
|
|
199
|
+
} else if (ev.type === "content_block_start" && ev.content_block?.type === "tool_use") {
|
|
200
|
+
toolUseBlocks.push(ev.content_block);
|
|
196
201
|
}
|
|
197
202
|
},
|
|
198
203
|
() => {
|
|
204
|
+
msg.content = toolUseBlocks;
|
|
199
205
|
const event2 = anthropicEventFrom(msg, opts);
|
|
200
206
|
if (event2) fireAndForget(tollgate.track(event2), opts.onError);
|
|
201
207
|
}
|
|
@@ -220,6 +226,7 @@ function openAIEventFrom(completion, opts) {
|
|
|
220
226
|
const usage = completion?.usage;
|
|
221
227
|
if (!usage) return null;
|
|
222
228
|
const runId = resolveRunId(opts, completion.id);
|
|
229
|
+
const toolCalls = completion.choices?.[0]?.message?.tool_calls?.length ?? 0;
|
|
223
230
|
const event = {
|
|
224
231
|
customerId: opts.customerId,
|
|
225
232
|
agentId: opts.agentId,
|
|
@@ -230,6 +237,7 @@ function openAIEventFrom(completion, opts) {
|
|
|
230
237
|
tokensOut: usage.completion_tokens ?? 0,
|
|
231
238
|
reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? 0,
|
|
232
239
|
cachedTokens: usage.prompt_tokens_details?.cached_tokens ?? 0,
|
|
240
|
+
toolCalls,
|
|
233
241
|
revenueUnitCents: resolveRevenue(opts, completion),
|
|
234
242
|
idempotencyKey: completion.id ?? `${runId}#${randomId()}`
|
|
235
243
|
};
|
|
@@ -244,16 +252,26 @@ function wrapOpenAI(client, tollgate, opts) {
|
|
|
244
252
|
let id;
|
|
245
253
|
let model;
|
|
246
254
|
let usage;
|
|
255
|
+
const toolCallIndices = /* @__PURE__ */ new Set();
|
|
247
256
|
return instrumentStream(
|
|
248
257
|
result,
|
|
249
258
|
(chunk) => {
|
|
250
259
|
if (chunk.id) id = chunk.id;
|
|
251
260
|
if (chunk.model) model = chunk.model;
|
|
252
261
|
if (chunk.usage) usage = chunk.usage;
|
|
262
|
+
for (const c of chunk.choices ?? []) {
|
|
263
|
+
for (const tc of c.delta?.tool_calls ?? []) {
|
|
264
|
+
if (tc.index !== void 0) toolCallIndices.add(tc.index);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
253
267
|
},
|
|
254
268
|
() => {
|
|
255
269
|
if (!usage) return;
|
|
256
|
-
const
|
|
270
|
+
const synth = { id, model, usage };
|
|
271
|
+
if (toolCallIndices.size > 0) {
|
|
272
|
+
synth.choices = [{ message: { tool_calls: new Array(toolCallIndices.size) } }];
|
|
273
|
+
}
|
|
274
|
+
const event2 = openAIEventFrom(synth, opts);
|
|
257
275
|
if (event2) fireAndForget(tollgate.track(event2), opts.onError);
|
|
258
276
|
}
|
|
259
277
|
);
|
|
@@ -276,7 +294,7 @@ function wrapOpenAI(client, tollgate, opts) {
|
|
|
276
294
|
}
|
|
277
295
|
});
|
|
278
296
|
}
|
|
279
|
-
function bedrockEventFrom(usage, model, opts, response = void 0) {
|
|
297
|
+
function bedrockEventFrom(usage, model, opts, response = void 0, toolCalls = 0) {
|
|
280
298
|
if (!usage) return null;
|
|
281
299
|
const runId = resolveRunId(opts, void 0);
|
|
282
300
|
const event = {
|
|
@@ -289,6 +307,7 @@ function bedrockEventFrom(usage, model, opts, response = void 0) {
|
|
|
289
307
|
tokensOut: usage.outputTokens ?? 0,
|
|
290
308
|
cachedTokens: usage.cacheReadInputTokens ?? 0,
|
|
291
309
|
cacheWrite5mTokens: usage.cacheWriteInputTokens ?? 0,
|
|
310
|
+
toolCalls,
|
|
292
311
|
revenueUnitCents: resolveRevenue(opts, response),
|
|
293
312
|
idempotencyKey: `${runId}#${randomId()}`
|
|
294
313
|
};
|
|
@@ -301,20 +320,23 @@ function wrapBedrock(client, tollgate, opts) {
|
|
|
301
320
|
const model = command?.input?.modelId ?? "unknown";
|
|
302
321
|
if (result?.stream && isAsyncIterable(result.stream)) {
|
|
303
322
|
let usage;
|
|
323
|
+
let streamToolCalls = 0;
|
|
304
324
|
result.stream = instrumentStream(
|
|
305
325
|
result.stream,
|
|
306
326
|
(ev) => {
|
|
307
327
|
if (ev.metadata?.usage) usage = ev.metadata.usage;
|
|
328
|
+
if (ev.contentBlockStart?.start?.toolUse) streamToolCalls++;
|
|
308
329
|
},
|
|
309
330
|
() => {
|
|
310
|
-
const event = bedrockEventFrom(usage, model, opts, result);
|
|
331
|
+
const event = bedrockEventFrom(usage, model, opts, result, streamToolCalls);
|
|
311
332
|
if (event) fireAndForget(tollgate.track(event), opts.onError);
|
|
312
333
|
}
|
|
313
334
|
);
|
|
314
335
|
return result;
|
|
315
336
|
}
|
|
316
337
|
if (result?.usage) {
|
|
317
|
-
const
|
|
338
|
+
const tc = result.output?.message?.content?.filter((b) => b.toolUse != null).length ?? 0;
|
|
339
|
+
const event = bedrockEventFrom(result.usage, model, opts, result, tc);
|
|
318
340
|
if (event) fireAndForget(tollgate.track(event), opts.onError);
|
|
319
341
|
}
|
|
320
342
|
return result;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/instrument.ts"],"names":["event"],"mappings":"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","file":"index.cjs","sourcesContent":["import type {\n Provider,\n RunOutcome,\n TrackEventInput,\n TrackResult,\n UpsertCustomerInput,\n UpsertCustomerResult,\n} from './types';\n\nexport interface TollgateClientOptions {\n /** Account API key (`tg_live_…`). Falls back to `process.env.TOLLGATE_API_KEY`. */\n apiKey?: string;\n /** Base URL of your Tollgate deployment. Defaults to `TOLLGATE_BASE_URL` or production. */\n baseUrl?: string;\n /** Per-request timeout in ms. Default 10_000. */\n timeoutMs?: number;\n /** Retry attempts on network error / 5xx / 429. Default 2. */\n maxRetries?: number;\n /** Custom fetch (for testing or non-standard runtimes). Defaults to global fetch. */\n fetch?: typeof fetch;\n}\n\nexport class TollgateError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n readonly body?: unknown,\n ) {\n super(message);\n this.name = 'TollgateError';\n }\n}\n\nconst DEFAULT_BASE_URL = 'https://tollgateai.vercel.app';\n\n/** Close out a run with its outcome (and, if resolved, the revenue it earns).\n * A resolution carries no provider usage, so `provider`/`model` are optional. */\nexport interface ResolveInput {\n /** The run being closed (must match the runId your usage events used). */\n runId: string;\n customerId: string;\n /** 'resolved' books revenue; 'escalated'/'failed' book none (cost still counts). */\n outcome: RunOutcome;\n /** Revenue in cents for a resolved run (e.g. 50 for $0.50). Ignored if not resolved. */\n revenueUnitCents?: number;\n agentId?: string;\n /** Idempotency key for the closing event. Defaults to `${runId}#resolve`. */\n idempotencyKey?: string;\n ts?: string;\n /** Rarely needed — a resolution isn't a provider call. Default 'anthropic'/'resolution'. */\n provider?: Provider;\n model?: string;\n}\n\nexport interface TollgateClient {\n /** Report a single usage event. Idempotent on `idempotencyKey`. */\n track(event: TrackEventInput): Promise<TrackResult>;\n /** Close a run with its outcome — book revenue once, only if resolved.\n * Convenience over `track()`: sends a zero-usage terminal event. */\n resolve(input: ResolveInput): Promise<TrackResult>;\n /** Create/update a customer and (optionally) its plan, in code. Call this\n * BEFORE sending usage so plan-priced revenue (esp. usage_based, which is\n * computed at ingest) is recognized from the first event. Idempotent. */\n upsertCustomer(input: UpsertCustomerInput): Promise<UpsertCustomerResult>;\n}\n\nfunction getEnv(key: string): string | undefined {\n // Guarded so the SDK works in browsers/edge runtimes without `process`.\n return typeof process !== 'undefined' ? process.env?.[key] : undefined;\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nexport function createTollgateClient(opts: TollgateClientOptions = {}): TollgateClient {\n const apiKey = opts.apiKey ?? getEnv('TOLLGATE_API_KEY');\n const baseUrl = (opts.baseUrl ?? getEnv('TOLLGATE_BASE_URL') ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n const timeoutMs = opts.timeoutMs ?? 10_000;\n const maxRetries = opts.maxRetries ?? 2;\n const doFetch = opts.fetch ?? globalThis.fetch;\n\n if (typeof doFetch !== 'function') {\n throw new TollgateError('No fetch implementation available — pass `fetch` in options.');\n }\n\n // Shared POST with timeout + retry (transient 5xx/429/network only). 200 and\n // 201 both count as success; deterministic 4xx fail fast.\n async function postJson<T>(path: string, body: unknown): Promise<T> {\n if (!apiKey) {\n throw new TollgateError('Missing API key — set opts.apiKey or TOLLGATE_API_KEY.');\n }\n\n let lastErr: unknown;\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await doFetch(`${baseUrl}${path}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (res.ok) {\n return (await res.json()) as T;\n }\n\n if (res.status >= 500 || res.status === 429) {\n lastErr = new TollgateError(`Tollgate request failed (${res.status})`, res.status);\n } else {\n const errBody = await res.json().catch(() => ({}));\n throw new TollgateError(`Tollgate request failed (${res.status})`, res.status, errBody);\n }\n } catch (err) {\n if (err instanceof TollgateError && err.status && err.status < 500 && err.status !== 429) {\n throw err;\n }\n lastErr = err;\n } finally {\n clearTimeout(timer);\n }\n\n if (attempt < maxRetries) {\n await sleep(2 ** attempt * 200); // 200ms, 400ms, …\n }\n }\n\n throw lastErr instanceof Error\n ? lastErr\n : new TollgateError('Tollgate request failed after retries');\n }\n\n function track(event: TrackEventInput): Promise<TrackResult> {\n return postJson<TrackResult>('/api/track', event);\n }\n\n function upsertCustomer(input: UpsertCustomerInput): Promise<UpsertCustomerResult> {\n return postJson<UpsertCustomerResult>('/api/sdk/customer', input);\n }\n\n function resolve(input: ResolveInput): Promise<TrackResult> {\n return track({\n customerId: input.customerId,\n runId: input.runId,\n agentId: input.agentId,\n // A resolution isn't a provider call; zero usage ⇒ zero cost.\n provider: input.provider ?? 'anthropic',\n model: input.model ?? 'resolution',\n tokensIn: 0,\n tokensOut: 0,\n outcome: input.outcome,\n revenueUnitCents: input.revenueUnitCents,\n idempotencyKey: input.idempotencyKey ?? `${input.runId}#resolve`,\n ts: input.ts,\n });\n }\n\n return { track, resolve, upsertCustomer };\n}\n","// Auto-instrumentation: wrap a provider client so every completion reports its\n// REAL usage to Tollgate — no manual token counting. Wrappers are structurally\n// typed, so this package never has to depend on the provider SDKs.\n//\n// Coverage is universal: OpenAI + Anthropic native, every OpenAI-compatible\n// gateway (set `provider: 'openai_compatible'`), and AWS Bedrock — each in both\n// non-streaming and streaming modes. Cost is always derived server-side from the\n// token counts these wrappers capture, so no provider needs to return a dollar\n// figure (pass `providerCostCents` only if you already have one).\n\nimport type { TollgateClient } from './client';\nimport type { Provider, TrackEventInput } from './types';\n\nexport interface InstrumentOptions {\n /** Your end customer's stable id. Required for margin attribution. */\n customerId: string;\n /** Optional agent/workflow id. */\n agentId?: string;\n /** Override the reported provider. Defaults per wrapper ('openai' /\n * 'anthropic' / 'bedrock'). Set to 'openai_compatible' when the client points\n * at an OpenAI-shaped gateway (Vercel AI Gateway, OpenRouter, Groq, Together,\n * Nebius, local vLLM, …) so the server prices it by the gateway-echoed model. */\n provider?: Provider;\n /** Revenue per call in cents (or a function of the response). */\n revenueUnitCents?: number | ((response: unknown) => number | undefined);\n /** Provider/gateway-reported cost in cents (or a function of the response).\n * When present and > 0, the server uses it verbatim and skips the rate card —\n * the authoritative escape hatch when you already have an exact cost. */\n providerCostCents?: number | ((response: unknown) => number | undefined);\n /** Override the run id; defaults to the provider response id. */\n runId?: string | (() => string);\n /** Called if a background track() fails. Defaults to console.warn. */\n onError?: (err: unknown) => void;\n}\n\nfunction randomId(): string {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n return `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction resolveRunId(opts: InstrumentOptions, responseId?: string): string {\n if (typeof opts.runId === 'function') return opts.runId();\n return opts.runId ?? responseId ?? randomId();\n}\n\nfunction resolveRevenue(opts: InstrumentOptions, response: unknown): number | undefined {\n return typeof opts.revenueUnitCents === 'function'\n ? opts.revenueUnitCents(response)\n : opts.revenueUnitCents;\n}\n\nfunction resolveCost(opts: InstrumentOptions, response: unknown): number | undefined {\n return typeof opts.providerCostCents === 'function'\n ? opts.providerCostCents(response)\n : opts.providerCostCents;\n}\n\n/** Attach providerCostCents to an event only when a (non-undefined) value resolves. */\nfunction withCost(\n event: TrackEventInput,\n opts: InstrumentOptions,\n response: unknown,\n): TrackEventInput {\n const cost = resolveCost(opts, response);\n if (cost !== undefined) event.providerCostCents = cost;\n return event;\n}\n\nfunction fireAndForget(p: Promise<unknown>, onError?: InstrumentOptions['onError']): void {\n p.catch((err) => (onError ?? ((e) => console.warn('[tollgate] track failed:', e)))(err));\n}\n\nfunction isAsyncIterable(x: unknown): x is AsyncIterable<unknown> {\n return x != null && typeof (x as Record<symbol, unknown>)[Symbol.asyncIterator] === 'function';\n}\n\n/**\n * Wrap an async iterable (a provider stream) so each chunk is observed and a\n * finalizer runs once the stream is exhausted — without disturbing the stream's\n * other methods (`.tee()`, `.controller`, …), which are proxied through. If the\n * consumer abandons the stream early the finalizer still fires on `.return()`.\n */\nfunction instrumentStream<TChunk>(\n stream: AsyncIterable<TChunk>,\n onChunk: (chunk: TChunk) => void,\n onDone: () => void,\n): AsyncIterable<TChunk> {\n let finished = false;\n const finish = () => {\n if (finished) return;\n finished = true;\n onDone();\n };\n return new Proxy(stream, {\n get(target, prop, recv) {\n if (prop === Symbol.asyncIterator) {\n return function instrumentedIterator() {\n const inner = (target as AsyncIterable<TChunk>)[Symbol.asyncIterator]();\n return {\n async next(...a: []) {\n const r = await inner.next(...a);\n if (r.done) finish();\n else onChunk(r.value);\n return r;\n },\n async return(v?: unknown) {\n finish();\n return inner.return ? inner.return(v) : { done: true, value: v as TChunk };\n },\n async throw(e?: unknown) {\n finish();\n if (inner.throw) return inner.throw(e);\n throw e;\n },\n [Symbol.asyncIterator]() {\n return this;\n },\n };\n };\n }\n return Reflect.get(target, prop, recv);\n },\n });\n}\n\n// --- Anthropic ------------------------------------------------------------\n\ninterface AnthropicUsage {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n // Cache CREATION (writes) bill above the input rate. Newer responses break the\n // total down by TTL; older ones only return the aggregate cache_creation count.\n cache_creation_input_tokens?: number;\n cache_creation?: {\n ephemeral_5m_input_tokens?: number;\n ephemeral_1h_input_tokens?: number;\n };\n}\ninterface AnthropicMessage {\n id?: string;\n model?: string;\n usage?: AnthropicUsage;\n}\n// Streaming event shapes we read usage from (message_start carries inputs +\n// cache, message_delta carries the cumulative output token count).\ninterface AnthropicStreamEvent {\n type?: string;\n message?: AnthropicMessage;\n usage?: AnthropicUsage;\n}\n\n/** Map a non-streaming Anthropic message to a track payload (or null if no usage). */\nexport function anthropicEventFrom(\n msg: AnthropicMessage,\n opts: InstrumentOptions,\n): TrackEventInput | null {\n const usage = msg?.usage;\n if (!usage) return null;\n const runId = resolveRunId(opts, msg.id);\n // Split cache-creation tokens by TTL when the response provides the breakdown;\n // otherwise attribute the whole cache_creation total to the default 5-minute TTL.\n const fivem = usage.cache_creation?.ephemeral_5m_input_tokens;\n const oneh = usage.cache_creation?.ephemeral_1h_input_tokens;\n const hasSplit = fivem !== undefined || oneh !== undefined;\n const event: TrackEventInput = {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n provider: opts.provider ?? 'anthropic',\n model: msg.model ?? 'unknown',\n tokensIn: usage.input_tokens ?? 0,\n tokensOut: usage.output_tokens ?? 0,\n cachedTokens: usage.cache_read_input_tokens ?? 0,\n cacheWrite5mTokens: hasSplit ? fivem ?? 0 : usage.cache_creation_input_tokens ?? 0,\n cacheWrite1hTokens: hasSplit ? oneh ?? 0 : 0,\n revenueUnitCents: resolveRevenue(opts, msg),\n idempotencyKey: msg.id ?? `${runId}#${randomId()}`,\n };\n return withCost(event, opts, msg);\n}\n\ninterface AnthropicLike {\n messages: { create: (...args: never[]) => Promise<unknown> };\n}\n\n/** Wrap an Anthropic client so `messages.create` auto-reports usage (streaming\n * and non-streaming). */\nexport function wrapAnthropic<T extends AnthropicLike>(\n client: T,\n tollgate: TollgateClient,\n opts: InstrumentOptions,\n): T {\n const messages = client.messages;\n const original = messages.create.bind(messages) as (...a: never[]) => Promise<unknown>;\n\n const create = async (...args: never[]): Promise<unknown> => {\n const result = await original(...args);\n if (isAsyncIterable(result)) {\n // Reconstruct a message from the event stream, then reuse the non-stream\n // mapper. input/cache tokens arrive in message_start; the final cumulative\n // output token count arrives in the last message_delta.\n const msg: AnthropicMessage = {};\n return instrumentStream(\n result as AsyncIterable<AnthropicStreamEvent>,\n (ev) => {\n if (ev.type === 'message_start' && ev.message) {\n msg.id = ev.message.id;\n msg.model = ev.message.model;\n msg.usage = { ...ev.message.usage };\n } else if (ev.type === 'message_delta' && ev.usage) {\n msg.usage = { ...(msg.usage ?? {}), output_tokens: ev.usage.output_tokens };\n }\n },\n () => {\n const event = anthropicEventFrom(msg, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n },\n );\n }\n const event = anthropicEventFrom(result as AnthropicMessage, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n return result;\n };\n\n return new Proxy(client, {\n get(target, prop, recv) {\n if (prop === 'messages') {\n return new Proxy(messages, {\n get: (m, p, r) => (p === 'create' ? create : Reflect.get(m, p, r)),\n });\n }\n return Reflect.get(target, prop, recv);\n },\n });\n}\n\n// --- OpenAI (and OpenAI-compatible gateways) ------------------------------\n\ninterface OpenAIUsage {\n prompt_tokens?: number;\n completion_tokens?: number;\n completion_tokens_details?: { reasoning_tokens?: number };\n prompt_tokens_details?: { cached_tokens?: number };\n}\ninterface OpenAICompletion {\n id?: string;\n model?: string;\n usage?: OpenAIUsage;\n}\n\n/** Map a non-streaming OpenAI chat completion to a track payload (or null). */\nexport function openAIEventFrom(\n completion: OpenAICompletion,\n opts: InstrumentOptions,\n): TrackEventInput | null {\n const usage = completion?.usage;\n if (!usage) return null;\n const runId = resolveRunId(opts, completion.id);\n const event: TrackEventInput = {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n provider: opts.provider ?? 'openai',\n model: completion.model ?? 'unknown',\n tokensIn: usage.prompt_tokens ?? 0,\n tokensOut: usage.completion_tokens ?? 0,\n reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? 0,\n cachedTokens: usage.prompt_tokens_details?.cached_tokens ?? 0,\n revenueUnitCents: resolveRevenue(opts, completion),\n idempotencyKey: completion.id ?? `${runId}#${randomId()}`,\n };\n return withCost(event, opts, completion);\n}\n\ninterface OpenAILike {\n chat: { completions: { create: (...args: never[]) => Promise<unknown> } };\n}\n\n/** Wrap an OpenAI (or OpenAI-compatible) client so `chat.completions.create`\n * auto-reports usage. Streaming works when the caller sets\n * `stream_options: { include_usage: true }` (required for OpenAI to emit a final\n * usage chunk); without it there are no token counts to report and the call is\n * passed through untouched. */\nexport function wrapOpenAI<T extends OpenAILike>(\n client: T,\n tollgate: TollgateClient,\n opts: InstrumentOptions,\n): T {\n const completions = client.chat.completions;\n const original = completions.create.bind(completions) as (...a: never[]) => Promise<unknown>;\n\n const create = async (...args: never[]): Promise<unknown> => {\n const result = await original(...args);\n if (isAsyncIterable(result)) {\n let id: string | undefined;\n let model: string | undefined;\n let usage: OpenAIUsage | undefined;\n return instrumentStream(\n result as AsyncIterable<OpenAICompletion>,\n (chunk) => {\n if (chunk.id) id = chunk.id;\n if (chunk.model) model = chunk.model;\n if (chunk.usage) usage = chunk.usage; // only the final chunk carries it\n },\n () => {\n if (!usage) return; // caller didn't request include_usage — nothing to report\n const event = openAIEventFrom({ id, model, usage }, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n },\n );\n }\n const event = openAIEventFrom(result as OpenAICompletion, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n return result;\n };\n\n return new Proxy(client, {\n get(target, prop, recv) {\n if (prop === 'chat') {\n const chat = Reflect.get(target, prop, recv) as OpenAILike['chat'];\n return new Proxy(chat, {\n get: (c, p, r) =>\n p === 'completions'\n ? new Proxy(completions, {\n get: (co, pp, rr) => (pp === 'create' ? create : Reflect.get(co, pp, rr)),\n })\n : Reflect.get(c, p, r),\n });\n }\n return Reflect.get(target, prop, recv);\n },\n });\n}\n\n// --- AWS Bedrock ----------------------------------------------------------\n// Bedrock's Converse API is command-based: the model id lives on the REQUEST,\n// not the response, so we read it from the command input. Usage is reported in\n// camelCase (inputTokens/outputTokens) plus optional cache token counts.\n\ninterface BedrockUsage {\n inputTokens?: number;\n outputTokens?: number;\n cacheReadInputTokens?: number;\n cacheWriteInputTokens?: number;\n}\ninterface BedrockConverseResponse {\n usage?: BedrockUsage;\n // ConverseStream returns an async-iterable `stream` of events; the final\n // `metadata` event carries the usage totals.\n stream?: AsyncIterable<{ metadata?: { usage?: BedrockUsage } }>;\n}\n\n/** Map a Bedrock Converse response (model from the request) to a track payload. */\nexport function bedrockEventFrom(\n usage: BedrockUsage | undefined,\n model: string,\n opts: InstrumentOptions,\n response: unknown = undefined,\n): TrackEventInput | null {\n if (!usage) return null;\n const runId = resolveRunId(opts, undefined);\n const event: TrackEventInput = {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n provider: opts.provider ?? 'bedrock',\n model,\n tokensIn: usage.inputTokens ?? 0,\n tokensOut: usage.outputTokens ?? 0,\n cachedTokens: usage.cacheReadInputTokens ?? 0,\n cacheWrite5mTokens: usage.cacheWriteInputTokens ?? 0,\n revenueUnitCents: resolveRevenue(opts, response),\n idempotencyKey: `${runId}#${randomId()}`,\n };\n return withCost(event, opts, response);\n}\n\ninterface BedrockLike {\n send: (command: unknown, ...rest: never[]) => Promise<unknown>;\n}\n\n/** Wrap a Bedrock Runtime client so `send(ConverseCommand)` /\n * `send(ConverseStreamCommand)` auto-report usage. Non-Converse commands (no\n * usage in the response) pass through untouched. */\nexport function wrapBedrock<T extends BedrockLike>(\n client: T,\n tollgate: TollgateClient,\n opts: InstrumentOptions,\n): T {\n const originalSend = client.send.bind(client) as BedrockLike['send'];\n\n const send = async (command: unknown, ...rest: never[]): Promise<unknown> => {\n const result = (await originalSend(command, ...rest)) as BedrockConverseResponse;\n const model =\n ((command as { input?: { modelId?: string } })?.input?.modelId) ?? 'unknown';\n\n if (result?.stream && isAsyncIterable(result.stream)) {\n let usage: BedrockUsage | undefined;\n result.stream = instrumentStream(\n result.stream,\n (ev) => {\n if (ev.metadata?.usage) usage = ev.metadata.usage;\n },\n () => {\n const event = bedrockEventFrom(usage, model, opts, result);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n },\n );\n return result;\n }\n\n if (result?.usage) {\n const event = bedrockEventFrom(result.usage, model, opts, result);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n }\n return result;\n };\n\n return new Proxy(client, {\n get(target, prop, recv) {\n if (prop === 'send') return send;\n return Reflect.get(target, prop, recv);\n },\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/instrument.ts"],"names":["event"],"mappings":"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","file":"index.cjs","sourcesContent":["import type {\n Provider,\n RunOutcome,\n TrackEventInput,\n TrackResult,\n UpsertCustomerInput,\n UpsertCustomerResult,\n} from './types';\n\nexport interface TollgateClientOptions {\n /** Account API key (`tg_live_…`). Falls back to `process.env.TOLLGATE_API_KEY`. */\n apiKey?: string;\n /** Base URL of your Tollgate deployment. Defaults to `TOLLGATE_BASE_URL` or production. */\n baseUrl?: string;\n /** Per-request timeout in ms. Default 10_000. */\n timeoutMs?: number;\n /** Retry attempts on network error / 5xx / 429. Default 2. */\n maxRetries?: number;\n /** Custom fetch (for testing or non-standard runtimes). Defaults to global fetch. */\n fetch?: typeof fetch;\n}\n\nexport class TollgateError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n readonly body?: unknown,\n ) {\n super(message);\n this.name = 'TollgateError';\n }\n}\n\nconst DEFAULT_BASE_URL = 'https://tollgateai.vercel.app';\n\n/** Close out a run with its outcome (and, if resolved, the revenue it earns).\n * A resolution carries no provider usage, so `provider`/`model` are optional. */\nexport interface ResolveInput {\n /** The run being closed (must match the runId your usage events used). */\n runId: string;\n customerId: string;\n /** 'resolved' books revenue; 'escalated'/'failed' book none (cost still counts). */\n outcome: RunOutcome;\n /** Revenue in cents for a resolved run (e.g. 50 for $0.50). Ignored if not resolved. */\n revenueUnitCents?: number;\n agentId?: string;\n /** Idempotency key for the closing event. Defaults to `${runId}#resolve`. */\n idempotencyKey?: string;\n ts?: string;\n /** Rarely needed — a resolution isn't a provider call. Default 'anthropic'/'resolution'. */\n provider?: Provider;\n model?: string;\n}\n\nexport interface TollgateClient {\n /** Report a single usage event. Idempotent on `idempotencyKey`. */\n track(event: TrackEventInput): Promise<TrackResult>;\n /** Close a run with its outcome — book revenue once, only if resolved.\n * Convenience over `track()`: sends a zero-usage terminal event. */\n resolve(input: ResolveInput): Promise<TrackResult>;\n /** Create/update a customer and (optionally) its plan, in code. Call this\n * BEFORE sending usage so plan-priced revenue (esp. usage_based, which is\n * computed at ingest) is recognized from the first event. Idempotent. */\n upsertCustomer(input: UpsertCustomerInput): Promise<UpsertCustomerResult>;\n}\n\nfunction getEnv(key: string): string | undefined {\n // Guarded so the SDK works in browsers/edge runtimes without `process`.\n return typeof process !== 'undefined' ? process.env?.[key] : undefined;\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nexport function createTollgateClient(opts: TollgateClientOptions = {}): TollgateClient {\n const apiKey = opts.apiKey ?? getEnv('TOLLGATE_API_KEY');\n const baseUrl = (opts.baseUrl ?? getEnv('TOLLGATE_BASE_URL') ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n const timeoutMs = opts.timeoutMs ?? 10_000;\n const maxRetries = opts.maxRetries ?? 2;\n const doFetch = opts.fetch ?? globalThis.fetch;\n\n if (typeof doFetch !== 'function') {\n throw new TollgateError('No fetch implementation available — pass `fetch` in options.');\n }\n\n // Shared POST with timeout + retry (transient 5xx/429/network only). 200 and\n // 201 both count as success; deterministic 4xx fail fast.\n async function postJson<T>(path: string, body: unknown): Promise<T> {\n if (!apiKey) {\n throw new TollgateError('Missing API key — set opts.apiKey or TOLLGATE_API_KEY.');\n }\n\n let lastErr: unknown;\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await doFetch(`${baseUrl}${path}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (res.ok) {\n return (await res.json()) as T;\n }\n\n if (res.status >= 500 || res.status === 429) {\n lastErr = new TollgateError(`Tollgate request failed (${res.status})`, res.status);\n } else {\n const errBody = await res.json().catch(() => ({}));\n throw new TollgateError(`Tollgate request failed (${res.status})`, res.status, errBody);\n }\n } catch (err) {\n if (err instanceof TollgateError && err.status && err.status < 500 && err.status !== 429) {\n throw err;\n }\n lastErr = err;\n } finally {\n clearTimeout(timer);\n }\n\n if (attempt < maxRetries) {\n await sleep(2 ** attempt * 200); // 200ms, 400ms, …\n }\n }\n\n throw lastErr instanceof Error\n ? lastErr\n : new TollgateError('Tollgate request failed after retries');\n }\n\n function track(event: TrackEventInput): Promise<TrackResult> {\n return postJson<TrackResult>('/api/track', event);\n }\n\n function upsertCustomer(input: UpsertCustomerInput): Promise<UpsertCustomerResult> {\n return postJson<UpsertCustomerResult>('/api/sdk/customer', input);\n }\n\n function resolve(input: ResolveInput): Promise<TrackResult> {\n return track({\n customerId: input.customerId,\n runId: input.runId,\n agentId: input.agentId,\n // A resolution isn't a provider call; zero usage ⇒ zero cost.\n provider: input.provider ?? 'anthropic',\n model: input.model ?? 'resolution',\n tokensIn: 0,\n tokensOut: 0,\n outcome: input.outcome,\n revenueUnitCents: input.revenueUnitCents,\n idempotencyKey: input.idempotencyKey ?? `${input.runId}#resolve`,\n ts: input.ts,\n });\n }\n\n return { track, resolve, upsertCustomer };\n}\n","// Auto-instrumentation: wrap a provider client so every completion reports its\n// REAL usage to Tollgate — no manual token counting. Wrappers are structurally\n// typed, so this package never has to depend on the provider SDKs.\n//\n// Coverage is universal: OpenAI + Anthropic native, every OpenAI-compatible\n// gateway (set `provider: 'openai_compatible'`), and AWS Bedrock — each in both\n// non-streaming and streaming modes. Cost is always derived server-side from the\n// token counts these wrappers capture, so no provider needs to return a dollar\n// figure (pass `providerCostCents` only if you already have one).\n\nimport type { TollgateClient } from './client';\nimport type { Provider, TrackEventInput } from './types';\n\nexport interface InstrumentOptions {\n /** Your end customer's stable id. Required for margin attribution. */\n customerId: string;\n /** Optional agent/workflow id. */\n agentId?: string;\n /** Override the reported provider. Defaults per wrapper ('openai' /\n * 'anthropic' / 'bedrock'). Set to 'openai_compatible' when the client points\n * at an OpenAI-shaped gateway (Vercel AI Gateway, OpenRouter, Groq, Together,\n * Nebius, local vLLM, …) so the server prices it by the gateway-echoed model. */\n provider?: Provider;\n /** Revenue per call in cents (or a function of the response). */\n revenueUnitCents?: number | ((response: unknown) => number | undefined);\n /** Provider/gateway-reported cost in cents (or a function of the response).\n * When present and > 0, the server uses it verbatim and skips the rate card —\n * the authoritative escape hatch when you already have an exact cost. */\n providerCostCents?: number | ((response: unknown) => number | undefined);\n /** Override the run id; defaults to the provider response id. */\n runId?: string | (() => string);\n /** Called if a background track() fails. Defaults to console.warn. */\n onError?: (err: unknown) => void;\n}\n\nfunction randomId(): string {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n return `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction resolveRunId(opts: InstrumentOptions, responseId?: string): string {\n if (typeof opts.runId === 'function') return opts.runId();\n return opts.runId ?? responseId ?? randomId();\n}\n\nfunction resolveRevenue(opts: InstrumentOptions, response: unknown): number | undefined {\n return typeof opts.revenueUnitCents === 'function'\n ? opts.revenueUnitCents(response)\n : opts.revenueUnitCents;\n}\n\nfunction resolveCost(opts: InstrumentOptions, response: unknown): number | undefined {\n return typeof opts.providerCostCents === 'function'\n ? opts.providerCostCents(response)\n : opts.providerCostCents;\n}\n\n/** Attach providerCostCents to an event only when a (non-undefined) value resolves. */\nfunction withCost(\n event: TrackEventInput,\n opts: InstrumentOptions,\n response: unknown,\n): TrackEventInput {\n const cost = resolveCost(opts, response);\n if (cost !== undefined) event.providerCostCents = cost;\n return event;\n}\n\nfunction fireAndForget(p: Promise<unknown>, onError?: InstrumentOptions['onError']): void {\n p.catch((err) => (onError ?? ((e) => console.warn('[tollgate] track failed:', e)))(err));\n}\n\nfunction isAsyncIterable(x: unknown): x is AsyncIterable<unknown> {\n return x != null && typeof (x as Record<symbol, unknown>)[Symbol.asyncIterator] === 'function';\n}\n\n/**\n * Wrap an async iterable (a provider stream) so each chunk is observed and a\n * finalizer runs once the stream is exhausted — without disturbing the stream's\n * other methods (`.tee()`, `.controller`, …), which are proxied through. If the\n * consumer abandons the stream early the finalizer still fires on `.return()`.\n */\nfunction instrumentStream<TChunk>(\n stream: AsyncIterable<TChunk>,\n onChunk: (chunk: TChunk) => void,\n onDone: () => void,\n): AsyncIterable<TChunk> {\n let finished = false;\n const finish = () => {\n if (finished) return;\n finished = true;\n onDone();\n };\n return new Proxy(stream, {\n get(target, prop, recv) {\n if (prop === Symbol.asyncIterator) {\n return function instrumentedIterator() {\n const inner = (target as AsyncIterable<TChunk>)[Symbol.asyncIterator]();\n return {\n async next(...a: []) {\n const r = await inner.next(...a);\n if (r.done) finish();\n else onChunk(r.value);\n return r;\n },\n async return(v?: unknown) {\n finish();\n return inner.return ? inner.return(v) : { done: true, value: v as TChunk };\n },\n async throw(e?: unknown) {\n finish();\n if (inner.throw) return inner.throw(e);\n throw e;\n },\n [Symbol.asyncIterator]() {\n return this;\n },\n };\n };\n }\n return Reflect.get(target, prop, recv);\n },\n });\n}\n\n// --- Anthropic ------------------------------------------------------------\n\ninterface AnthropicUsage {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n // Cache CREATION (writes) bill above the input rate. Newer responses break the\n // total down by TTL; older ones only return the aggregate cache_creation count.\n cache_creation_input_tokens?: number;\n cache_creation?: {\n ephemeral_5m_input_tokens?: number;\n ephemeral_1h_input_tokens?: number;\n };\n}\ninterface AnthropicContentBlock {\n type?: string;\n}\ninterface AnthropicMessage {\n id?: string;\n model?: string;\n usage?: AnthropicUsage;\n content?: AnthropicContentBlock[];\n}\n// Streaming event shapes we read usage from (message_start carries inputs +\n// cache, message_delta carries the cumulative output token count).\ninterface AnthropicStreamEvent {\n type?: string;\n message?: AnthropicMessage;\n usage?: AnthropicUsage;\n content_block?: AnthropicContentBlock;\n}\n\n/** Map a non-streaming Anthropic message to a track payload (or null if no usage). */\nexport function anthropicEventFrom(\n msg: AnthropicMessage,\n opts: InstrumentOptions,\n): TrackEventInput | null {\n const usage = msg?.usage;\n if (!usage) return null;\n const runId = resolveRunId(opts, msg.id);\n // Split cache-creation tokens by TTL when the response provides the breakdown;\n // otherwise attribute the whole cache_creation total to the default 5-minute TTL.\n const fivem = usage.cache_creation?.ephemeral_5m_input_tokens;\n const oneh = usage.cache_creation?.ephemeral_1h_input_tokens;\n const hasSplit = fivem !== undefined || oneh !== undefined;\n const toolCalls = Array.isArray(msg.content)\n ? msg.content.filter((b) => b.type === 'tool_use').length\n : 0;\n const event: TrackEventInput = {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n provider: opts.provider ?? 'anthropic',\n model: msg.model ?? 'unknown',\n tokensIn: usage.input_tokens ?? 0,\n tokensOut: usage.output_tokens ?? 0,\n cachedTokens: usage.cache_read_input_tokens ?? 0,\n cacheWrite5mTokens: hasSplit ? fivem ?? 0 : usage.cache_creation_input_tokens ?? 0,\n cacheWrite1hTokens: hasSplit ? oneh ?? 0 : 0,\n toolCalls,\n revenueUnitCents: resolveRevenue(opts, msg),\n idempotencyKey: msg.id ?? `${runId}#${randomId()}`,\n };\n return withCost(event, opts, msg);\n}\n\ninterface AnthropicLike {\n messages: { create: (...args: never[]) => Promise<unknown> };\n}\n\n/** Wrap an Anthropic client so `messages.create` auto-reports usage (streaming\n * and non-streaming). */\nexport function wrapAnthropic<T extends AnthropicLike>(\n client: T,\n tollgate: TollgateClient,\n opts: InstrumentOptions,\n): T {\n const messages = client.messages;\n const original = messages.create.bind(messages) as (...a: never[]) => Promise<unknown>;\n\n const create = async (...args: never[]): Promise<unknown> => {\n const result = await original(...args);\n if (isAsyncIterable(result)) {\n // Reconstruct a message from the event stream, then reuse the non-stream\n // mapper. input/cache tokens arrive in message_start; the final cumulative\n // output token count arrives in the last message_delta.\n const msg: AnthropicMessage = {};\n const toolUseBlocks: AnthropicContentBlock[] = [];\n return instrumentStream(\n result as AsyncIterable<AnthropicStreamEvent>,\n (ev) => {\n if (ev.type === 'message_start' && ev.message) {\n msg.id = ev.message.id;\n msg.model = ev.message.model;\n msg.usage = { ...ev.message.usage };\n } else if (ev.type === 'message_delta' && ev.usage) {\n msg.usage = { ...(msg.usage ?? {}), output_tokens: ev.usage.output_tokens };\n } else if (ev.type === 'content_block_start' && ev.content_block?.type === 'tool_use') {\n toolUseBlocks.push(ev.content_block);\n }\n },\n () => {\n msg.content = toolUseBlocks;\n const event = anthropicEventFrom(msg, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n },\n );\n }\n const event = anthropicEventFrom(result as AnthropicMessage, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n return result;\n };\n\n return new Proxy(client, {\n get(target, prop, recv) {\n if (prop === 'messages') {\n return new Proxy(messages, {\n get: (m, p, r) => (p === 'create' ? create : Reflect.get(m, p, r)),\n });\n }\n return Reflect.get(target, prop, recv);\n },\n });\n}\n\n// --- OpenAI (and OpenAI-compatible gateways) ------------------------------\n\ninterface OpenAIUsage {\n prompt_tokens?: number;\n completion_tokens?: number;\n completion_tokens_details?: { reasoning_tokens?: number };\n prompt_tokens_details?: { cached_tokens?: number };\n}\ninterface OpenAIToolCall {\n index?: number;\n}\ninterface OpenAIDelta {\n tool_calls?: OpenAIToolCall[];\n}\ninterface OpenAIChoice {\n message?: { tool_calls?: unknown[] };\n delta?: OpenAIDelta;\n}\ninterface OpenAICompletion {\n id?: string;\n model?: string;\n usage?: OpenAIUsage;\n choices?: OpenAIChoice[];\n}\n\n/** Map a non-streaming OpenAI chat completion to a track payload (or null). */\nexport function openAIEventFrom(\n completion: OpenAICompletion,\n opts: InstrumentOptions,\n): TrackEventInput | null {\n const usage = completion?.usage;\n if (!usage) return null;\n const runId = resolveRunId(opts, completion.id);\n const toolCalls = completion.choices?.[0]?.message?.tool_calls?.length ?? 0;\n const event: TrackEventInput = {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n provider: opts.provider ?? 'openai',\n model: completion.model ?? 'unknown',\n tokensIn: usage.prompt_tokens ?? 0,\n tokensOut: usage.completion_tokens ?? 0,\n reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? 0,\n cachedTokens: usage.prompt_tokens_details?.cached_tokens ?? 0,\n toolCalls,\n revenueUnitCents: resolveRevenue(opts, completion),\n idempotencyKey: completion.id ?? `${runId}#${randomId()}`,\n };\n return withCost(event, opts, completion);\n}\n\ninterface OpenAILike {\n chat: { completions: { create: (...args: never[]) => Promise<unknown> } };\n}\n\n/** Wrap an OpenAI (or OpenAI-compatible) client so `chat.completions.create`\n * auto-reports usage. Streaming works when the caller sets\n * `stream_options: { include_usage: true }` (required for OpenAI to emit a final\n * usage chunk); without it there are no token counts to report and the call is\n * passed through untouched. */\nexport function wrapOpenAI<T extends OpenAILike>(\n client: T,\n tollgate: TollgateClient,\n opts: InstrumentOptions,\n): T {\n const completions = client.chat.completions;\n const original = completions.create.bind(completions) as (...a: never[]) => Promise<unknown>;\n\n const create = async (...args: never[]): Promise<unknown> => {\n const result = await original(...args);\n if (isAsyncIterable(result)) {\n let id: string | undefined;\n let model: string | undefined;\n let usage: OpenAIUsage | undefined;\n const toolCallIndices = new Set<number>();\n return instrumentStream(\n result as AsyncIterable<OpenAICompletion>,\n (chunk) => {\n if (chunk.id) id = chunk.id;\n if (chunk.model) model = chunk.model;\n if (chunk.usage) usage = chunk.usage;\n for (const c of chunk.choices ?? []) {\n for (const tc of c.delta?.tool_calls ?? []) {\n if (tc.index !== undefined) toolCallIndices.add(tc.index);\n }\n }\n },\n () => {\n if (!usage) return;\n const synth: OpenAICompletion = { id, model, usage };\n if (toolCallIndices.size > 0) {\n synth.choices = [{ message: { tool_calls: new Array(toolCallIndices.size) } }];\n }\n const event = openAIEventFrom(synth, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n },\n );\n }\n const event = openAIEventFrom(result as OpenAICompletion, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n return result;\n };\n\n return new Proxy(client, {\n get(target, prop, recv) {\n if (prop === 'chat') {\n const chat = Reflect.get(target, prop, recv) as OpenAILike['chat'];\n return new Proxy(chat, {\n get: (c, p, r) =>\n p === 'completions'\n ? new Proxy(completions, {\n get: (co, pp, rr) => (pp === 'create' ? create : Reflect.get(co, pp, rr)),\n })\n : Reflect.get(c, p, r),\n });\n }\n return Reflect.get(target, prop, recv);\n },\n });\n}\n\n// --- AWS Bedrock ----------------------------------------------------------\n// Bedrock's Converse API is command-based: the model id lives on the REQUEST,\n// not the response, so we read it from the command input. Usage is reported in\n// camelCase (inputTokens/outputTokens) plus optional cache token counts.\n\ninterface BedrockUsage {\n inputTokens?: number;\n outputTokens?: number;\n cacheReadInputTokens?: number;\n cacheWriteInputTokens?: number;\n}\ninterface BedrockContentBlock {\n toolUse?: unknown;\n}\ninterface BedrockConverseResponse {\n usage?: BedrockUsage;\n output?: { message?: { content?: BedrockContentBlock[] } };\n // ConverseStream returns an async-iterable `stream` of events; the final\n // `metadata` event carries the usage totals.\n stream?: AsyncIterable<{\n metadata?: { usage?: BedrockUsage };\n contentBlockStart?: { start?: { toolUse?: unknown } };\n }>;\n}\n\n/** Map a Bedrock Converse response (model from the request) to a track payload. */\nexport function bedrockEventFrom(\n usage: BedrockUsage | undefined,\n model: string,\n opts: InstrumentOptions,\n response: unknown = undefined,\n toolCalls = 0,\n): TrackEventInput | null {\n if (!usage) return null;\n const runId = resolveRunId(opts, undefined);\n const event: TrackEventInput = {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n provider: opts.provider ?? 'bedrock',\n model,\n tokensIn: usage.inputTokens ?? 0,\n tokensOut: usage.outputTokens ?? 0,\n cachedTokens: usage.cacheReadInputTokens ?? 0,\n cacheWrite5mTokens: usage.cacheWriteInputTokens ?? 0,\n toolCalls,\n revenueUnitCents: resolveRevenue(opts, response),\n idempotencyKey: `${runId}#${randomId()}`,\n };\n return withCost(event, opts, response);\n}\n\ninterface BedrockLike {\n send: (command: unknown, ...rest: never[]) => Promise<unknown>;\n}\n\n/** Wrap a Bedrock Runtime client so `send(ConverseCommand)` /\n * `send(ConverseStreamCommand)` auto-report usage. Non-Converse commands (no\n * usage in the response) pass through untouched. */\nexport function wrapBedrock<T extends BedrockLike>(\n client: T,\n tollgate: TollgateClient,\n opts: InstrumentOptions,\n): T {\n const originalSend = client.send.bind(client) as BedrockLike['send'];\n\n const send = async (command: unknown, ...rest: never[]): Promise<unknown> => {\n const result = (await originalSend(command, ...rest)) as BedrockConverseResponse;\n const model =\n ((command as { input?: { modelId?: string } })?.input?.modelId) ?? 'unknown';\n\n if (result?.stream && isAsyncIterable(result.stream)) {\n let usage: BedrockUsage | undefined;\n let streamToolCalls = 0;\n result.stream = instrumentStream(\n result.stream,\n (ev) => {\n if (ev.metadata?.usage) usage = ev.metadata.usage;\n if (ev.contentBlockStart?.start?.toolUse) streamToolCalls++;\n },\n () => {\n const event = bedrockEventFrom(usage, model, opts, result, streamToolCalls);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n },\n );\n return result;\n }\n\n if (result?.usage) {\n const tc = result.output?.message?.content?.filter((b) => b.toolUse != null).length ?? 0;\n const event = bedrockEventFrom(result.usage, model, opts, result, tc);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n }\n return result;\n };\n\n return new Proxy(client, {\n get(target, prop, recv) {\n if (prop === 'send') return send;\n return Reflect.get(target, prop, recv);\n },\n });\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -28,6 +28,11 @@ interface TrackEventInput {
|
|
|
28
28
|
cacheWrite1hTokens?: number;
|
|
29
29
|
toolCalls?: number;
|
|
30
30
|
toolName?: string;
|
|
31
|
+
/** Cost in cents of external tools/services used in this event (image
|
|
32
|
+
* generation, code sandbox, search APIs, vector DB lookups, etc.).
|
|
33
|
+
* Added directly to the cost breakdown — no rate-card lookup needed.
|
|
34
|
+
* Fractional cents accepted (e.g. 1.5 for $0.015). */
|
|
35
|
+
externalCostCents?: number;
|
|
31
36
|
/** Terminal run outcome, set on the run's closing event only (omit on
|
|
32
37
|
* intermediate steps). Gates outcome-priced revenue. See `resolve()`. */
|
|
33
38
|
outcome?: RunOutcome;
|
|
@@ -157,10 +162,14 @@ interface AnthropicUsage {
|
|
|
157
162
|
ephemeral_1h_input_tokens?: number;
|
|
158
163
|
};
|
|
159
164
|
}
|
|
165
|
+
interface AnthropicContentBlock {
|
|
166
|
+
type?: string;
|
|
167
|
+
}
|
|
160
168
|
interface AnthropicMessage {
|
|
161
169
|
id?: string;
|
|
162
170
|
model?: string;
|
|
163
171
|
usage?: AnthropicUsage;
|
|
172
|
+
content?: AnthropicContentBlock[];
|
|
164
173
|
}
|
|
165
174
|
/** Map a non-streaming Anthropic message to a track payload (or null if no usage). */
|
|
166
175
|
declare function anthropicEventFrom(msg: AnthropicMessage, opts: InstrumentOptions): TrackEventInput | null;
|
|
@@ -182,10 +191,23 @@ interface OpenAIUsage {
|
|
|
182
191
|
cached_tokens?: number;
|
|
183
192
|
};
|
|
184
193
|
}
|
|
194
|
+
interface OpenAIToolCall {
|
|
195
|
+
index?: number;
|
|
196
|
+
}
|
|
197
|
+
interface OpenAIDelta {
|
|
198
|
+
tool_calls?: OpenAIToolCall[];
|
|
199
|
+
}
|
|
200
|
+
interface OpenAIChoice {
|
|
201
|
+
message?: {
|
|
202
|
+
tool_calls?: unknown[];
|
|
203
|
+
};
|
|
204
|
+
delta?: OpenAIDelta;
|
|
205
|
+
}
|
|
185
206
|
interface OpenAICompletion {
|
|
186
207
|
id?: string;
|
|
187
208
|
model?: string;
|
|
188
209
|
usage?: OpenAIUsage;
|
|
210
|
+
choices?: OpenAIChoice[];
|
|
189
211
|
}
|
|
190
212
|
/** Map a non-streaming OpenAI chat completion to a track payload (or null). */
|
|
191
213
|
declare function openAIEventFrom(completion: OpenAICompletion, opts: InstrumentOptions): TrackEventInput | null;
|
|
@@ -209,7 +231,7 @@ interface BedrockUsage {
|
|
|
209
231
|
cacheWriteInputTokens?: number;
|
|
210
232
|
}
|
|
211
233
|
/** Map a Bedrock Converse response (model from the request) to a track payload. */
|
|
212
|
-
declare function bedrockEventFrom(usage: BedrockUsage | undefined, model: string, opts: InstrumentOptions, response?: unknown): TrackEventInput | null;
|
|
234
|
+
declare function bedrockEventFrom(usage: BedrockUsage | undefined, model: string, opts: InstrumentOptions, response?: unknown, toolCalls?: number): TrackEventInput | null;
|
|
213
235
|
interface BedrockLike {
|
|
214
236
|
send: (command: unknown, ...rest: never[]) => Promise<unknown>;
|
|
215
237
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,11 @@ interface TrackEventInput {
|
|
|
28
28
|
cacheWrite1hTokens?: number;
|
|
29
29
|
toolCalls?: number;
|
|
30
30
|
toolName?: string;
|
|
31
|
+
/** Cost in cents of external tools/services used in this event (image
|
|
32
|
+
* generation, code sandbox, search APIs, vector DB lookups, etc.).
|
|
33
|
+
* Added directly to the cost breakdown — no rate-card lookup needed.
|
|
34
|
+
* Fractional cents accepted (e.g. 1.5 for $0.015). */
|
|
35
|
+
externalCostCents?: number;
|
|
31
36
|
/** Terminal run outcome, set on the run's closing event only (omit on
|
|
32
37
|
* intermediate steps). Gates outcome-priced revenue. See `resolve()`. */
|
|
33
38
|
outcome?: RunOutcome;
|
|
@@ -157,10 +162,14 @@ interface AnthropicUsage {
|
|
|
157
162
|
ephemeral_1h_input_tokens?: number;
|
|
158
163
|
};
|
|
159
164
|
}
|
|
165
|
+
interface AnthropicContentBlock {
|
|
166
|
+
type?: string;
|
|
167
|
+
}
|
|
160
168
|
interface AnthropicMessage {
|
|
161
169
|
id?: string;
|
|
162
170
|
model?: string;
|
|
163
171
|
usage?: AnthropicUsage;
|
|
172
|
+
content?: AnthropicContentBlock[];
|
|
164
173
|
}
|
|
165
174
|
/** Map a non-streaming Anthropic message to a track payload (or null if no usage). */
|
|
166
175
|
declare function anthropicEventFrom(msg: AnthropicMessage, opts: InstrumentOptions): TrackEventInput | null;
|
|
@@ -182,10 +191,23 @@ interface OpenAIUsage {
|
|
|
182
191
|
cached_tokens?: number;
|
|
183
192
|
};
|
|
184
193
|
}
|
|
194
|
+
interface OpenAIToolCall {
|
|
195
|
+
index?: number;
|
|
196
|
+
}
|
|
197
|
+
interface OpenAIDelta {
|
|
198
|
+
tool_calls?: OpenAIToolCall[];
|
|
199
|
+
}
|
|
200
|
+
interface OpenAIChoice {
|
|
201
|
+
message?: {
|
|
202
|
+
tool_calls?: unknown[];
|
|
203
|
+
};
|
|
204
|
+
delta?: OpenAIDelta;
|
|
205
|
+
}
|
|
185
206
|
interface OpenAICompletion {
|
|
186
207
|
id?: string;
|
|
187
208
|
model?: string;
|
|
188
209
|
usage?: OpenAIUsage;
|
|
210
|
+
choices?: OpenAIChoice[];
|
|
189
211
|
}
|
|
190
212
|
/** Map a non-streaming OpenAI chat completion to a track payload (or null). */
|
|
191
213
|
declare function openAIEventFrom(completion: OpenAICompletion, opts: InstrumentOptions): TrackEventInput | null;
|
|
@@ -209,7 +231,7 @@ interface BedrockUsage {
|
|
|
209
231
|
cacheWriteInputTokens?: number;
|
|
210
232
|
}
|
|
211
233
|
/** Map a Bedrock Converse response (model from the request) to a track payload. */
|
|
212
|
-
declare function bedrockEventFrom(usage: BedrockUsage | undefined, model: string, opts: InstrumentOptions, response?: unknown): TrackEventInput | null;
|
|
234
|
+
declare function bedrockEventFrom(usage: BedrockUsage | undefined, model: string, opts: InstrumentOptions, response?: unknown, toolCalls?: number): TrackEventInput | null;
|
|
213
235
|
interface BedrockLike {
|
|
214
236
|
send: (command: unknown, ...rest: never[]) => Promise<unknown>;
|
|
215
237
|
}
|
package/dist/index.js
CHANGED
|
@@ -159,6 +159,7 @@ function anthropicEventFrom(msg, opts) {
|
|
|
159
159
|
const fivem = usage.cache_creation?.ephemeral_5m_input_tokens;
|
|
160
160
|
const oneh = usage.cache_creation?.ephemeral_1h_input_tokens;
|
|
161
161
|
const hasSplit = fivem !== void 0 || oneh !== void 0;
|
|
162
|
+
const toolCalls = Array.isArray(msg.content) ? msg.content.filter((b) => b.type === "tool_use").length : 0;
|
|
162
163
|
const event = {
|
|
163
164
|
customerId: opts.customerId,
|
|
164
165
|
agentId: opts.agentId,
|
|
@@ -170,6 +171,7 @@ function anthropicEventFrom(msg, opts) {
|
|
|
170
171
|
cachedTokens: usage.cache_read_input_tokens ?? 0,
|
|
171
172
|
cacheWrite5mTokens: hasSplit ? fivem ?? 0 : usage.cache_creation_input_tokens ?? 0,
|
|
172
173
|
cacheWrite1hTokens: hasSplit ? oneh ?? 0 : 0,
|
|
174
|
+
toolCalls,
|
|
173
175
|
revenueUnitCents: resolveRevenue(opts, msg),
|
|
174
176
|
idempotencyKey: msg.id ?? `${runId}#${randomId()}`
|
|
175
177
|
};
|
|
@@ -182,6 +184,7 @@ function wrapAnthropic(client, tollgate, opts) {
|
|
|
182
184
|
const result = await original(...args);
|
|
183
185
|
if (isAsyncIterable(result)) {
|
|
184
186
|
const msg = {};
|
|
187
|
+
const toolUseBlocks = [];
|
|
185
188
|
return instrumentStream(
|
|
186
189
|
result,
|
|
187
190
|
(ev) => {
|
|
@@ -191,9 +194,12 @@ function wrapAnthropic(client, tollgate, opts) {
|
|
|
191
194
|
msg.usage = { ...ev.message.usage };
|
|
192
195
|
} else if (ev.type === "message_delta" && ev.usage) {
|
|
193
196
|
msg.usage = { ...msg.usage ?? {}, output_tokens: ev.usage.output_tokens };
|
|
197
|
+
} else if (ev.type === "content_block_start" && ev.content_block?.type === "tool_use") {
|
|
198
|
+
toolUseBlocks.push(ev.content_block);
|
|
194
199
|
}
|
|
195
200
|
},
|
|
196
201
|
() => {
|
|
202
|
+
msg.content = toolUseBlocks;
|
|
197
203
|
const event2 = anthropicEventFrom(msg, opts);
|
|
198
204
|
if (event2) fireAndForget(tollgate.track(event2), opts.onError);
|
|
199
205
|
}
|
|
@@ -218,6 +224,7 @@ function openAIEventFrom(completion, opts) {
|
|
|
218
224
|
const usage = completion?.usage;
|
|
219
225
|
if (!usage) return null;
|
|
220
226
|
const runId = resolveRunId(opts, completion.id);
|
|
227
|
+
const toolCalls = completion.choices?.[0]?.message?.tool_calls?.length ?? 0;
|
|
221
228
|
const event = {
|
|
222
229
|
customerId: opts.customerId,
|
|
223
230
|
agentId: opts.agentId,
|
|
@@ -228,6 +235,7 @@ function openAIEventFrom(completion, opts) {
|
|
|
228
235
|
tokensOut: usage.completion_tokens ?? 0,
|
|
229
236
|
reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? 0,
|
|
230
237
|
cachedTokens: usage.prompt_tokens_details?.cached_tokens ?? 0,
|
|
238
|
+
toolCalls,
|
|
231
239
|
revenueUnitCents: resolveRevenue(opts, completion),
|
|
232
240
|
idempotencyKey: completion.id ?? `${runId}#${randomId()}`
|
|
233
241
|
};
|
|
@@ -242,16 +250,26 @@ function wrapOpenAI(client, tollgate, opts) {
|
|
|
242
250
|
let id;
|
|
243
251
|
let model;
|
|
244
252
|
let usage;
|
|
253
|
+
const toolCallIndices = /* @__PURE__ */ new Set();
|
|
245
254
|
return instrumentStream(
|
|
246
255
|
result,
|
|
247
256
|
(chunk) => {
|
|
248
257
|
if (chunk.id) id = chunk.id;
|
|
249
258
|
if (chunk.model) model = chunk.model;
|
|
250
259
|
if (chunk.usage) usage = chunk.usage;
|
|
260
|
+
for (const c of chunk.choices ?? []) {
|
|
261
|
+
for (const tc of c.delta?.tool_calls ?? []) {
|
|
262
|
+
if (tc.index !== void 0) toolCallIndices.add(tc.index);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
251
265
|
},
|
|
252
266
|
() => {
|
|
253
267
|
if (!usage) return;
|
|
254
|
-
const
|
|
268
|
+
const synth = { id, model, usage };
|
|
269
|
+
if (toolCallIndices.size > 0) {
|
|
270
|
+
synth.choices = [{ message: { tool_calls: new Array(toolCallIndices.size) } }];
|
|
271
|
+
}
|
|
272
|
+
const event2 = openAIEventFrom(synth, opts);
|
|
255
273
|
if (event2) fireAndForget(tollgate.track(event2), opts.onError);
|
|
256
274
|
}
|
|
257
275
|
);
|
|
@@ -274,7 +292,7 @@ function wrapOpenAI(client, tollgate, opts) {
|
|
|
274
292
|
}
|
|
275
293
|
});
|
|
276
294
|
}
|
|
277
|
-
function bedrockEventFrom(usage, model, opts, response = void 0) {
|
|
295
|
+
function bedrockEventFrom(usage, model, opts, response = void 0, toolCalls = 0) {
|
|
278
296
|
if (!usage) return null;
|
|
279
297
|
const runId = resolveRunId(opts, void 0);
|
|
280
298
|
const event = {
|
|
@@ -287,6 +305,7 @@ function bedrockEventFrom(usage, model, opts, response = void 0) {
|
|
|
287
305
|
tokensOut: usage.outputTokens ?? 0,
|
|
288
306
|
cachedTokens: usage.cacheReadInputTokens ?? 0,
|
|
289
307
|
cacheWrite5mTokens: usage.cacheWriteInputTokens ?? 0,
|
|
308
|
+
toolCalls,
|
|
290
309
|
revenueUnitCents: resolveRevenue(opts, response),
|
|
291
310
|
idempotencyKey: `${runId}#${randomId()}`
|
|
292
311
|
};
|
|
@@ -299,20 +318,23 @@ function wrapBedrock(client, tollgate, opts) {
|
|
|
299
318
|
const model = command?.input?.modelId ?? "unknown";
|
|
300
319
|
if (result?.stream && isAsyncIterable(result.stream)) {
|
|
301
320
|
let usage;
|
|
321
|
+
let streamToolCalls = 0;
|
|
302
322
|
result.stream = instrumentStream(
|
|
303
323
|
result.stream,
|
|
304
324
|
(ev) => {
|
|
305
325
|
if (ev.metadata?.usage) usage = ev.metadata.usage;
|
|
326
|
+
if (ev.contentBlockStart?.start?.toolUse) streamToolCalls++;
|
|
306
327
|
},
|
|
307
328
|
() => {
|
|
308
|
-
const event = bedrockEventFrom(usage, model, opts, result);
|
|
329
|
+
const event = bedrockEventFrom(usage, model, opts, result, streamToolCalls);
|
|
309
330
|
if (event) fireAndForget(tollgate.track(event), opts.onError);
|
|
310
331
|
}
|
|
311
332
|
);
|
|
312
333
|
return result;
|
|
313
334
|
}
|
|
314
335
|
if (result?.usage) {
|
|
315
|
-
const
|
|
336
|
+
const tc = result.output?.message?.content?.filter((b) => b.toolUse != null).length ?? 0;
|
|
337
|
+
const event = bedrockEventFrom(result.usage, model, opts, result, tc);
|
|
316
338
|
if (event) fireAndForget(tollgate.track(event), opts.onError);
|
|
317
339
|
}
|
|
318
340
|
return result;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/instrument.ts"],"names":["event"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n Provider,\n RunOutcome,\n TrackEventInput,\n TrackResult,\n UpsertCustomerInput,\n UpsertCustomerResult,\n} from './types';\n\nexport interface TollgateClientOptions {\n /** Account API key (`tg_live_…`). Falls back to `process.env.TOLLGATE_API_KEY`. */\n apiKey?: string;\n /** Base URL of your Tollgate deployment. Defaults to `TOLLGATE_BASE_URL` or production. */\n baseUrl?: string;\n /** Per-request timeout in ms. Default 10_000. */\n timeoutMs?: number;\n /** Retry attempts on network error / 5xx / 429. Default 2. */\n maxRetries?: number;\n /** Custom fetch (for testing or non-standard runtimes). Defaults to global fetch. */\n fetch?: typeof fetch;\n}\n\nexport class TollgateError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n readonly body?: unknown,\n ) {\n super(message);\n this.name = 'TollgateError';\n }\n}\n\nconst DEFAULT_BASE_URL = 'https://tollgateai.vercel.app';\n\n/** Close out a run with its outcome (and, if resolved, the revenue it earns).\n * A resolution carries no provider usage, so `provider`/`model` are optional. */\nexport interface ResolveInput {\n /** The run being closed (must match the runId your usage events used). */\n runId: string;\n customerId: string;\n /** 'resolved' books revenue; 'escalated'/'failed' book none (cost still counts). */\n outcome: RunOutcome;\n /** Revenue in cents for a resolved run (e.g. 50 for $0.50). Ignored if not resolved. */\n revenueUnitCents?: number;\n agentId?: string;\n /** Idempotency key for the closing event. Defaults to `${runId}#resolve`. */\n idempotencyKey?: string;\n ts?: string;\n /** Rarely needed — a resolution isn't a provider call. Default 'anthropic'/'resolution'. */\n provider?: Provider;\n model?: string;\n}\n\nexport interface TollgateClient {\n /** Report a single usage event. Idempotent on `idempotencyKey`. */\n track(event: TrackEventInput): Promise<TrackResult>;\n /** Close a run with its outcome — book revenue once, only if resolved.\n * Convenience over `track()`: sends a zero-usage terminal event. */\n resolve(input: ResolveInput): Promise<TrackResult>;\n /** Create/update a customer and (optionally) its plan, in code. Call this\n * BEFORE sending usage so plan-priced revenue (esp. usage_based, which is\n * computed at ingest) is recognized from the first event. Idempotent. */\n upsertCustomer(input: UpsertCustomerInput): Promise<UpsertCustomerResult>;\n}\n\nfunction getEnv(key: string): string | undefined {\n // Guarded so the SDK works in browsers/edge runtimes without `process`.\n return typeof process !== 'undefined' ? process.env?.[key] : undefined;\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nexport function createTollgateClient(opts: TollgateClientOptions = {}): TollgateClient {\n const apiKey = opts.apiKey ?? getEnv('TOLLGATE_API_KEY');\n const baseUrl = (opts.baseUrl ?? getEnv('TOLLGATE_BASE_URL') ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n const timeoutMs = opts.timeoutMs ?? 10_000;\n const maxRetries = opts.maxRetries ?? 2;\n const doFetch = opts.fetch ?? globalThis.fetch;\n\n if (typeof doFetch !== 'function') {\n throw new TollgateError('No fetch implementation available — pass `fetch` in options.');\n }\n\n // Shared POST with timeout + retry (transient 5xx/429/network only). 200 and\n // 201 both count as success; deterministic 4xx fail fast.\n async function postJson<T>(path: string, body: unknown): Promise<T> {\n if (!apiKey) {\n throw new TollgateError('Missing API key — set opts.apiKey or TOLLGATE_API_KEY.');\n }\n\n let lastErr: unknown;\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await doFetch(`${baseUrl}${path}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (res.ok) {\n return (await res.json()) as T;\n }\n\n if (res.status >= 500 || res.status === 429) {\n lastErr = new TollgateError(`Tollgate request failed (${res.status})`, res.status);\n } else {\n const errBody = await res.json().catch(() => ({}));\n throw new TollgateError(`Tollgate request failed (${res.status})`, res.status, errBody);\n }\n } catch (err) {\n if (err instanceof TollgateError && err.status && err.status < 500 && err.status !== 429) {\n throw err;\n }\n lastErr = err;\n } finally {\n clearTimeout(timer);\n }\n\n if (attempt < maxRetries) {\n await sleep(2 ** attempt * 200); // 200ms, 400ms, …\n }\n }\n\n throw lastErr instanceof Error\n ? lastErr\n : new TollgateError('Tollgate request failed after retries');\n }\n\n function track(event: TrackEventInput): Promise<TrackResult> {\n return postJson<TrackResult>('/api/track', event);\n }\n\n function upsertCustomer(input: UpsertCustomerInput): Promise<UpsertCustomerResult> {\n return postJson<UpsertCustomerResult>('/api/sdk/customer', input);\n }\n\n function resolve(input: ResolveInput): Promise<TrackResult> {\n return track({\n customerId: input.customerId,\n runId: input.runId,\n agentId: input.agentId,\n // A resolution isn't a provider call; zero usage ⇒ zero cost.\n provider: input.provider ?? 'anthropic',\n model: input.model ?? 'resolution',\n tokensIn: 0,\n tokensOut: 0,\n outcome: input.outcome,\n revenueUnitCents: input.revenueUnitCents,\n idempotencyKey: input.idempotencyKey ?? `${input.runId}#resolve`,\n ts: input.ts,\n });\n }\n\n return { track, resolve, upsertCustomer };\n}\n","// Auto-instrumentation: wrap a provider client so every completion reports its\n// REAL usage to Tollgate — no manual token counting. Wrappers are structurally\n// typed, so this package never has to depend on the provider SDKs.\n//\n// Coverage is universal: OpenAI + Anthropic native, every OpenAI-compatible\n// gateway (set `provider: 'openai_compatible'`), and AWS Bedrock — each in both\n// non-streaming and streaming modes. Cost is always derived server-side from the\n// token counts these wrappers capture, so no provider needs to return a dollar\n// figure (pass `providerCostCents` only if you already have one).\n\nimport type { TollgateClient } from './client';\nimport type { Provider, TrackEventInput } from './types';\n\nexport interface InstrumentOptions {\n /** Your end customer's stable id. Required for margin attribution. */\n customerId: string;\n /** Optional agent/workflow id. */\n agentId?: string;\n /** Override the reported provider. Defaults per wrapper ('openai' /\n * 'anthropic' / 'bedrock'). Set to 'openai_compatible' when the client points\n * at an OpenAI-shaped gateway (Vercel AI Gateway, OpenRouter, Groq, Together,\n * Nebius, local vLLM, …) so the server prices it by the gateway-echoed model. */\n provider?: Provider;\n /** Revenue per call in cents (or a function of the response). */\n revenueUnitCents?: number | ((response: unknown) => number | undefined);\n /** Provider/gateway-reported cost in cents (or a function of the response).\n * When present and > 0, the server uses it verbatim and skips the rate card —\n * the authoritative escape hatch when you already have an exact cost. */\n providerCostCents?: number | ((response: unknown) => number | undefined);\n /** Override the run id; defaults to the provider response id. */\n runId?: string | (() => string);\n /** Called if a background track() fails. Defaults to console.warn. */\n onError?: (err: unknown) => void;\n}\n\nfunction randomId(): string {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n return `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction resolveRunId(opts: InstrumentOptions, responseId?: string): string {\n if (typeof opts.runId === 'function') return opts.runId();\n return opts.runId ?? responseId ?? randomId();\n}\n\nfunction resolveRevenue(opts: InstrumentOptions, response: unknown): number | undefined {\n return typeof opts.revenueUnitCents === 'function'\n ? opts.revenueUnitCents(response)\n : opts.revenueUnitCents;\n}\n\nfunction resolveCost(opts: InstrumentOptions, response: unknown): number | undefined {\n return typeof opts.providerCostCents === 'function'\n ? opts.providerCostCents(response)\n : opts.providerCostCents;\n}\n\n/** Attach providerCostCents to an event only when a (non-undefined) value resolves. */\nfunction withCost(\n event: TrackEventInput,\n opts: InstrumentOptions,\n response: unknown,\n): TrackEventInput {\n const cost = resolveCost(opts, response);\n if (cost !== undefined) event.providerCostCents = cost;\n return event;\n}\n\nfunction fireAndForget(p: Promise<unknown>, onError?: InstrumentOptions['onError']): void {\n p.catch((err) => (onError ?? ((e) => console.warn('[tollgate] track failed:', e)))(err));\n}\n\nfunction isAsyncIterable(x: unknown): x is AsyncIterable<unknown> {\n return x != null && typeof (x as Record<symbol, unknown>)[Symbol.asyncIterator] === 'function';\n}\n\n/**\n * Wrap an async iterable (a provider stream) so each chunk is observed and a\n * finalizer runs once the stream is exhausted — without disturbing the stream's\n * other methods (`.tee()`, `.controller`, …), which are proxied through. If the\n * consumer abandons the stream early the finalizer still fires on `.return()`.\n */\nfunction instrumentStream<TChunk>(\n stream: AsyncIterable<TChunk>,\n onChunk: (chunk: TChunk) => void,\n onDone: () => void,\n): AsyncIterable<TChunk> {\n let finished = false;\n const finish = () => {\n if (finished) return;\n finished = true;\n onDone();\n };\n return new Proxy(stream, {\n get(target, prop, recv) {\n if (prop === Symbol.asyncIterator) {\n return function instrumentedIterator() {\n const inner = (target as AsyncIterable<TChunk>)[Symbol.asyncIterator]();\n return {\n async next(...a: []) {\n const r = await inner.next(...a);\n if (r.done) finish();\n else onChunk(r.value);\n return r;\n },\n async return(v?: unknown) {\n finish();\n return inner.return ? inner.return(v) : { done: true, value: v as TChunk };\n },\n async throw(e?: unknown) {\n finish();\n if (inner.throw) return inner.throw(e);\n throw e;\n },\n [Symbol.asyncIterator]() {\n return this;\n },\n };\n };\n }\n return Reflect.get(target, prop, recv);\n },\n });\n}\n\n// --- Anthropic ------------------------------------------------------------\n\ninterface AnthropicUsage {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n // Cache CREATION (writes) bill above the input rate. Newer responses break the\n // total down by TTL; older ones only return the aggregate cache_creation count.\n cache_creation_input_tokens?: number;\n cache_creation?: {\n ephemeral_5m_input_tokens?: number;\n ephemeral_1h_input_tokens?: number;\n };\n}\ninterface AnthropicMessage {\n id?: string;\n model?: string;\n usage?: AnthropicUsage;\n}\n// Streaming event shapes we read usage from (message_start carries inputs +\n// cache, message_delta carries the cumulative output token count).\ninterface AnthropicStreamEvent {\n type?: string;\n message?: AnthropicMessage;\n usage?: AnthropicUsage;\n}\n\n/** Map a non-streaming Anthropic message to a track payload (or null if no usage). */\nexport function anthropicEventFrom(\n msg: AnthropicMessage,\n opts: InstrumentOptions,\n): TrackEventInput | null {\n const usage = msg?.usage;\n if (!usage) return null;\n const runId = resolveRunId(opts, msg.id);\n // Split cache-creation tokens by TTL when the response provides the breakdown;\n // otherwise attribute the whole cache_creation total to the default 5-minute TTL.\n const fivem = usage.cache_creation?.ephemeral_5m_input_tokens;\n const oneh = usage.cache_creation?.ephemeral_1h_input_tokens;\n const hasSplit = fivem !== undefined || oneh !== undefined;\n const event: TrackEventInput = {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n provider: opts.provider ?? 'anthropic',\n model: msg.model ?? 'unknown',\n tokensIn: usage.input_tokens ?? 0,\n tokensOut: usage.output_tokens ?? 0,\n cachedTokens: usage.cache_read_input_tokens ?? 0,\n cacheWrite5mTokens: hasSplit ? fivem ?? 0 : usage.cache_creation_input_tokens ?? 0,\n cacheWrite1hTokens: hasSplit ? oneh ?? 0 : 0,\n revenueUnitCents: resolveRevenue(opts, msg),\n idempotencyKey: msg.id ?? `${runId}#${randomId()}`,\n };\n return withCost(event, opts, msg);\n}\n\ninterface AnthropicLike {\n messages: { create: (...args: never[]) => Promise<unknown> };\n}\n\n/** Wrap an Anthropic client so `messages.create` auto-reports usage (streaming\n * and non-streaming). */\nexport function wrapAnthropic<T extends AnthropicLike>(\n client: T,\n tollgate: TollgateClient,\n opts: InstrumentOptions,\n): T {\n const messages = client.messages;\n const original = messages.create.bind(messages) as (...a: never[]) => Promise<unknown>;\n\n const create = async (...args: never[]): Promise<unknown> => {\n const result = await original(...args);\n if (isAsyncIterable(result)) {\n // Reconstruct a message from the event stream, then reuse the non-stream\n // mapper. input/cache tokens arrive in message_start; the final cumulative\n // output token count arrives in the last message_delta.\n const msg: AnthropicMessage = {};\n return instrumentStream(\n result as AsyncIterable<AnthropicStreamEvent>,\n (ev) => {\n if (ev.type === 'message_start' && ev.message) {\n msg.id = ev.message.id;\n msg.model = ev.message.model;\n msg.usage = { ...ev.message.usage };\n } else if (ev.type === 'message_delta' && ev.usage) {\n msg.usage = { ...(msg.usage ?? {}), output_tokens: ev.usage.output_tokens };\n }\n },\n () => {\n const event = anthropicEventFrom(msg, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n },\n );\n }\n const event = anthropicEventFrom(result as AnthropicMessage, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n return result;\n };\n\n return new Proxy(client, {\n get(target, prop, recv) {\n if (prop === 'messages') {\n return new Proxy(messages, {\n get: (m, p, r) => (p === 'create' ? create : Reflect.get(m, p, r)),\n });\n }\n return Reflect.get(target, prop, recv);\n },\n });\n}\n\n// --- OpenAI (and OpenAI-compatible gateways) ------------------------------\n\ninterface OpenAIUsage {\n prompt_tokens?: number;\n completion_tokens?: number;\n completion_tokens_details?: { reasoning_tokens?: number };\n prompt_tokens_details?: { cached_tokens?: number };\n}\ninterface OpenAICompletion {\n id?: string;\n model?: string;\n usage?: OpenAIUsage;\n}\n\n/** Map a non-streaming OpenAI chat completion to a track payload (or null). */\nexport function openAIEventFrom(\n completion: OpenAICompletion,\n opts: InstrumentOptions,\n): TrackEventInput | null {\n const usage = completion?.usage;\n if (!usage) return null;\n const runId = resolveRunId(opts, completion.id);\n const event: TrackEventInput = {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n provider: opts.provider ?? 'openai',\n model: completion.model ?? 'unknown',\n tokensIn: usage.prompt_tokens ?? 0,\n tokensOut: usage.completion_tokens ?? 0,\n reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? 0,\n cachedTokens: usage.prompt_tokens_details?.cached_tokens ?? 0,\n revenueUnitCents: resolveRevenue(opts, completion),\n idempotencyKey: completion.id ?? `${runId}#${randomId()}`,\n };\n return withCost(event, opts, completion);\n}\n\ninterface OpenAILike {\n chat: { completions: { create: (...args: never[]) => Promise<unknown> } };\n}\n\n/** Wrap an OpenAI (or OpenAI-compatible) client so `chat.completions.create`\n * auto-reports usage. Streaming works when the caller sets\n * `stream_options: { include_usage: true }` (required for OpenAI to emit a final\n * usage chunk); without it there are no token counts to report and the call is\n * passed through untouched. */\nexport function wrapOpenAI<T extends OpenAILike>(\n client: T,\n tollgate: TollgateClient,\n opts: InstrumentOptions,\n): T {\n const completions = client.chat.completions;\n const original = completions.create.bind(completions) as (...a: never[]) => Promise<unknown>;\n\n const create = async (...args: never[]): Promise<unknown> => {\n const result = await original(...args);\n if (isAsyncIterable(result)) {\n let id: string | undefined;\n let model: string | undefined;\n let usage: OpenAIUsage | undefined;\n return instrumentStream(\n result as AsyncIterable<OpenAICompletion>,\n (chunk) => {\n if (chunk.id) id = chunk.id;\n if (chunk.model) model = chunk.model;\n if (chunk.usage) usage = chunk.usage; // only the final chunk carries it\n },\n () => {\n if (!usage) return; // caller didn't request include_usage — nothing to report\n const event = openAIEventFrom({ id, model, usage }, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n },\n );\n }\n const event = openAIEventFrom(result as OpenAICompletion, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n return result;\n };\n\n return new Proxy(client, {\n get(target, prop, recv) {\n if (prop === 'chat') {\n const chat = Reflect.get(target, prop, recv) as OpenAILike['chat'];\n return new Proxy(chat, {\n get: (c, p, r) =>\n p === 'completions'\n ? new Proxy(completions, {\n get: (co, pp, rr) => (pp === 'create' ? create : Reflect.get(co, pp, rr)),\n })\n : Reflect.get(c, p, r),\n });\n }\n return Reflect.get(target, prop, recv);\n },\n });\n}\n\n// --- AWS Bedrock ----------------------------------------------------------\n// Bedrock's Converse API is command-based: the model id lives on the REQUEST,\n// not the response, so we read it from the command input. Usage is reported in\n// camelCase (inputTokens/outputTokens) plus optional cache token counts.\n\ninterface BedrockUsage {\n inputTokens?: number;\n outputTokens?: number;\n cacheReadInputTokens?: number;\n cacheWriteInputTokens?: number;\n}\ninterface BedrockConverseResponse {\n usage?: BedrockUsage;\n // ConverseStream returns an async-iterable `stream` of events; the final\n // `metadata` event carries the usage totals.\n stream?: AsyncIterable<{ metadata?: { usage?: BedrockUsage } }>;\n}\n\n/** Map a Bedrock Converse response (model from the request) to a track payload. */\nexport function bedrockEventFrom(\n usage: BedrockUsage | undefined,\n model: string,\n opts: InstrumentOptions,\n response: unknown = undefined,\n): TrackEventInput | null {\n if (!usage) return null;\n const runId = resolveRunId(opts, undefined);\n const event: TrackEventInput = {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n provider: opts.provider ?? 'bedrock',\n model,\n tokensIn: usage.inputTokens ?? 0,\n tokensOut: usage.outputTokens ?? 0,\n cachedTokens: usage.cacheReadInputTokens ?? 0,\n cacheWrite5mTokens: usage.cacheWriteInputTokens ?? 0,\n revenueUnitCents: resolveRevenue(opts, response),\n idempotencyKey: `${runId}#${randomId()}`,\n };\n return withCost(event, opts, response);\n}\n\ninterface BedrockLike {\n send: (command: unknown, ...rest: never[]) => Promise<unknown>;\n}\n\n/** Wrap a Bedrock Runtime client so `send(ConverseCommand)` /\n * `send(ConverseStreamCommand)` auto-report usage. Non-Converse commands (no\n * usage in the response) pass through untouched. */\nexport function wrapBedrock<T extends BedrockLike>(\n client: T,\n tollgate: TollgateClient,\n opts: InstrumentOptions,\n): T {\n const originalSend = client.send.bind(client) as BedrockLike['send'];\n\n const send = async (command: unknown, ...rest: never[]): Promise<unknown> => {\n const result = (await originalSend(command, ...rest)) as BedrockConverseResponse;\n const model =\n ((command as { input?: { modelId?: string } })?.input?.modelId) ?? 'unknown';\n\n if (result?.stream && isAsyncIterable(result.stream)) {\n let usage: BedrockUsage | undefined;\n result.stream = instrumentStream(\n result.stream,\n (ev) => {\n if (ev.metadata?.usage) usage = ev.metadata.usage;\n },\n () => {\n const event = bedrockEventFrom(usage, model, opts, result);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n },\n );\n return result;\n }\n\n if (result?.usage) {\n const event = bedrockEventFrom(result.usage, model, opts, result);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n }\n return result;\n };\n\n return new Proxy(client, {\n get(target, prop, recv) {\n if (prop === 'send') return send;\n return Reflect.get(target, prop, recv);\n },\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/instrument.ts"],"names":["event"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n Provider,\n RunOutcome,\n TrackEventInput,\n TrackResult,\n UpsertCustomerInput,\n UpsertCustomerResult,\n} from './types';\n\nexport interface TollgateClientOptions {\n /** Account API key (`tg_live_…`). Falls back to `process.env.TOLLGATE_API_KEY`. */\n apiKey?: string;\n /** Base URL of your Tollgate deployment. Defaults to `TOLLGATE_BASE_URL` or production. */\n baseUrl?: string;\n /** Per-request timeout in ms. Default 10_000. */\n timeoutMs?: number;\n /** Retry attempts on network error / 5xx / 429. Default 2. */\n maxRetries?: number;\n /** Custom fetch (for testing or non-standard runtimes). Defaults to global fetch. */\n fetch?: typeof fetch;\n}\n\nexport class TollgateError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n readonly body?: unknown,\n ) {\n super(message);\n this.name = 'TollgateError';\n }\n}\n\nconst DEFAULT_BASE_URL = 'https://tollgateai.vercel.app';\n\n/** Close out a run with its outcome (and, if resolved, the revenue it earns).\n * A resolution carries no provider usage, so `provider`/`model` are optional. */\nexport interface ResolveInput {\n /** The run being closed (must match the runId your usage events used). */\n runId: string;\n customerId: string;\n /** 'resolved' books revenue; 'escalated'/'failed' book none (cost still counts). */\n outcome: RunOutcome;\n /** Revenue in cents for a resolved run (e.g. 50 for $0.50). Ignored if not resolved. */\n revenueUnitCents?: number;\n agentId?: string;\n /** Idempotency key for the closing event. Defaults to `${runId}#resolve`. */\n idempotencyKey?: string;\n ts?: string;\n /** Rarely needed — a resolution isn't a provider call. Default 'anthropic'/'resolution'. */\n provider?: Provider;\n model?: string;\n}\n\nexport interface TollgateClient {\n /** Report a single usage event. Idempotent on `idempotencyKey`. */\n track(event: TrackEventInput): Promise<TrackResult>;\n /** Close a run with its outcome — book revenue once, only if resolved.\n * Convenience over `track()`: sends a zero-usage terminal event. */\n resolve(input: ResolveInput): Promise<TrackResult>;\n /** Create/update a customer and (optionally) its plan, in code. Call this\n * BEFORE sending usage so plan-priced revenue (esp. usage_based, which is\n * computed at ingest) is recognized from the first event. Idempotent. */\n upsertCustomer(input: UpsertCustomerInput): Promise<UpsertCustomerResult>;\n}\n\nfunction getEnv(key: string): string | undefined {\n // Guarded so the SDK works in browsers/edge runtimes without `process`.\n return typeof process !== 'undefined' ? process.env?.[key] : undefined;\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nexport function createTollgateClient(opts: TollgateClientOptions = {}): TollgateClient {\n const apiKey = opts.apiKey ?? getEnv('TOLLGATE_API_KEY');\n const baseUrl = (opts.baseUrl ?? getEnv('TOLLGATE_BASE_URL') ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n const timeoutMs = opts.timeoutMs ?? 10_000;\n const maxRetries = opts.maxRetries ?? 2;\n const doFetch = opts.fetch ?? globalThis.fetch;\n\n if (typeof doFetch !== 'function') {\n throw new TollgateError('No fetch implementation available — pass `fetch` in options.');\n }\n\n // Shared POST with timeout + retry (transient 5xx/429/network only). 200 and\n // 201 both count as success; deterministic 4xx fail fast.\n async function postJson<T>(path: string, body: unknown): Promise<T> {\n if (!apiKey) {\n throw new TollgateError('Missing API key — set opts.apiKey or TOLLGATE_API_KEY.');\n }\n\n let lastErr: unknown;\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await doFetch(`${baseUrl}${path}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (res.ok) {\n return (await res.json()) as T;\n }\n\n if (res.status >= 500 || res.status === 429) {\n lastErr = new TollgateError(`Tollgate request failed (${res.status})`, res.status);\n } else {\n const errBody = await res.json().catch(() => ({}));\n throw new TollgateError(`Tollgate request failed (${res.status})`, res.status, errBody);\n }\n } catch (err) {\n if (err instanceof TollgateError && err.status && err.status < 500 && err.status !== 429) {\n throw err;\n }\n lastErr = err;\n } finally {\n clearTimeout(timer);\n }\n\n if (attempt < maxRetries) {\n await sleep(2 ** attempt * 200); // 200ms, 400ms, …\n }\n }\n\n throw lastErr instanceof Error\n ? lastErr\n : new TollgateError('Tollgate request failed after retries');\n }\n\n function track(event: TrackEventInput): Promise<TrackResult> {\n return postJson<TrackResult>('/api/track', event);\n }\n\n function upsertCustomer(input: UpsertCustomerInput): Promise<UpsertCustomerResult> {\n return postJson<UpsertCustomerResult>('/api/sdk/customer', input);\n }\n\n function resolve(input: ResolveInput): Promise<TrackResult> {\n return track({\n customerId: input.customerId,\n runId: input.runId,\n agentId: input.agentId,\n // A resolution isn't a provider call; zero usage ⇒ zero cost.\n provider: input.provider ?? 'anthropic',\n model: input.model ?? 'resolution',\n tokensIn: 0,\n tokensOut: 0,\n outcome: input.outcome,\n revenueUnitCents: input.revenueUnitCents,\n idempotencyKey: input.idempotencyKey ?? `${input.runId}#resolve`,\n ts: input.ts,\n });\n }\n\n return { track, resolve, upsertCustomer };\n}\n","// Auto-instrumentation: wrap a provider client so every completion reports its\n// REAL usage to Tollgate — no manual token counting. Wrappers are structurally\n// typed, so this package never has to depend on the provider SDKs.\n//\n// Coverage is universal: OpenAI + Anthropic native, every OpenAI-compatible\n// gateway (set `provider: 'openai_compatible'`), and AWS Bedrock — each in both\n// non-streaming and streaming modes. Cost is always derived server-side from the\n// token counts these wrappers capture, so no provider needs to return a dollar\n// figure (pass `providerCostCents` only if you already have one).\n\nimport type { TollgateClient } from './client';\nimport type { Provider, TrackEventInput } from './types';\n\nexport interface InstrumentOptions {\n /** Your end customer's stable id. Required for margin attribution. */\n customerId: string;\n /** Optional agent/workflow id. */\n agentId?: string;\n /** Override the reported provider. Defaults per wrapper ('openai' /\n * 'anthropic' / 'bedrock'). Set to 'openai_compatible' when the client points\n * at an OpenAI-shaped gateway (Vercel AI Gateway, OpenRouter, Groq, Together,\n * Nebius, local vLLM, …) so the server prices it by the gateway-echoed model. */\n provider?: Provider;\n /** Revenue per call in cents (or a function of the response). */\n revenueUnitCents?: number | ((response: unknown) => number | undefined);\n /** Provider/gateway-reported cost in cents (or a function of the response).\n * When present and > 0, the server uses it verbatim and skips the rate card —\n * the authoritative escape hatch when you already have an exact cost. */\n providerCostCents?: number | ((response: unknown) => number | undefined);\n /** Override the run id; defaults to the provider response id. */\n runId?: string | (() => string);\n /** Called if a background track() fails. Defaults to console.warn. */\n onError?: (err: unknown) => void;\n}\n\nfunction randomId(): string {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n return `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction resolveRunId(opts: InstrumentOptions, responseId?: string): string {\n if (typeof opts.runId === 'function') return opts.runId();\n return opts.runId ?? responseId ?? randomId();\n}\n\nfunction resolveRevenue(opts: InstrumentOptions, response: unknown): number | undefined {\n return typeof opts.revenueUnitCents === 'function'\n ? opts.revenueUnitCents(response)\n : opts.revenueUnitCents;\n}\n\nfunction resolveCost(opts: InstrumentOptions, response: unknown): number | undefined {\n return typeof opts.providerCostCents === 'function'\n ? opts.providerCostCents(response)\n : opts.providerCostCents;\n}\n\n/** Attach providerCostCents to an event only when a (non-undefined) value resolves. */\nfunction withCost(\n event: TrackEventInput,\n opts: InstrumentOptions,\n response: unknown,\n): TrackEventInput {\n const cost = resolveCost(opts, response);\n if (cost !== undefined) event.providerCostCents = cost;\n return event;\n}\n\nfunction fireAndForget(p: Promise<unknown>, onError?: InstrumentOptions['onError']): void {\n p.catch((err) => (onError ?? ((e) => console.warn('[tollgate] track failed:', e)))(err));\n}\n\nfunction isAsyncIterable(x: unknown): x is AsyncIterable<unknown> {\n return x != null && typeof (x as Record<symbol, unknown>)[Symbol.asyncIterator] === 'function';\n}\n\n/**\n * Wrap an async iterable (a provider stream) so each chunk is observed and a\n * finalizer runs once the stream is exhausted — without disturbing the stream's\n * other methods (`.tee()`, `.controller`, …), which are proxied through. If the\n * consumer abandons the stream early the finalizer still fires on `.return()`.\n */\nfunction instrumentStream<TChunk>(\n stream: AsyncIterable<TChunk>,\n onChunk: (chunk: TChunk) => void,\n onDone: () => void,\n): AsyncIterable<TChunk> {\n let finished = false;\n const finish = () => {\n if (finished) return;\n finished = true;\n onDone();\n };\n return new Proxy(stream, {\n get(target, prop, recv) {\n if (prop === Symbol.asyncIterator) {\n return function instrumentedIterator() {\n const inner = (target as AsyncIterable<TChunk>)[Symbol.asyncIterator]();\n return {\n async next(...a: []) {\n const r = await inner.next(...a);\n if (r.done) finish();\n else onChunk(r.value);\n return r;\n },\n async return(v?: unknown) {\n finish();\n return inner.return ? inner.return(v) : { done: true, value: v as TChunk };\n },\n async throw(e?: unknown) {\n finish();\n if (inner.throw) return inner.throw(e);\n throw e;\n },\n [Symbol.asyncIterator]() {\n return this;\n },\n };\n };\n }\n return Reflect.get(target, prop, recv);\n },\n });\n}\n\n// --- Anthropic ------------------------------------------------------------\n\ninterface AnthropicUsage {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n // Cache CREATION (writes) bill above the input rate. Newer responses break the\n // total down by TTL; older ones only return the aggregate cache_creation count.\n cache_creation_input_tokens?: number;\n cache_creation?: {\n ephemeral_5m_input_tokens?: number;\n ephemeral_1h_input_tokens?: number;\n };\n}\ninterface AnthropicContentBlock {\n type?: string;\n}\ninterface AnthropicMessage {\n id?: string;\n model?: string;\n usage?: AnthropicUsage;\n content?: AnthropicContentBlock[];\n}\n// Streaming event shapes we read usage from (message_start carries inputs +\n// cache, message_delta carries the cumulative output token count).\ninterface AnthropicStreamEvent {\n type?: string;\n message?: AnthropicMessage;\n usage?: AnthropicUsage;\n content_block?: AnthropicContentBlock;\n}\n\n/** Map a non-streaming Anthropic message to a track payload (or null if no usage). */\nexport function anthropicEventFrom(\n msg: AnthropicMessage,\n opts: InstrumentOptions,\n): TrackEventInput | null {\n const usage = msg?.usage;\n if (!usage) return null;\n const runId = resolveRunId(opts, msg.id);\n // Split cache-creation tokens by TTL when the response provides the breakdown;\n // otherwise attribute the whole cache_creation total to the default 5-minute TTL.\n const fivem = usage.cache_creation?.ephemeral_5m_input_tokens;\n const oneh = usage.cache_creation?.ephemeral_1h_input_tokens;\n const hasSplit = fivem !== undefined || oneh !== undefined;\n const toolCalls = Array.isArray(msg.content)\n ? msg.content.filter((b) => b.type === 'tool_use').length\n : 0;\n const event: TrackEventInput = {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n provider: opts.provider ?? 'anthropic',\n model: msg.model ?? 'unknown',\n tokensIn: usage.input_tokens ?? 0,\n tokensOut: usage.output_tokens ?? 0,\n cachedTokens: usage.cache_read_input_tokens ?? 0,\n cacheWrite5mTokens: hasSplit ? fivem ?? 0 : usage.cache_creation_input_tokens ?? 0,\n cacheWrite1hTokens: hasSplit ? oneh ?? 0 : 0,\n toolCalls,\n revenueUnitCents: resolveRevenue(opts, msg),\n idempotencyKey: msg.id ?? `${runId}#${randomId()}`,\n };\n return withCost(event, opts, msg);\n}\n\ninterface AnthropicLike {\n messages: { create: (...args: never[]) => Promise<unknown> };\n}\n\n/** Wrap an Anthropic client so `messages.create` auto-reports usage (streaming\n * and non-streaming). */\nexport function wrapAnthropic<T extends AnthropicLike>(\n client: T,\n tollgate: TollgateClient,\n opts: InstrumentOptions,\n): T {\n const messages = client.messages;\n const original = messages.create.bind(messages) as (...a: never[]) => Promise<unknown>;\n\n const create = async (...args: never[]): Promise<unknown> => {\n const result = await original(...args);\n if (isAsyncIterable(result)) {\n // Reconstruct a message from the event stream, then reuse the non-stream\n // mapper. input/cache tokens arrive in message_start; the final cumulative\n // output token count arrives in the last message_delta.\n const msg: AnthropicMessage = {};\n const toolUseBlocks: AnthropicContentBlock[] = [];\n return instrumentStream(\n result as AsyncIterable<AnthropicStreamEvent>,\n (ev) => {\n if (ev.type === 'message_start' && ev.message) {\n msg.id = ev.message.id;\n msg.model = ev.message.model;\n msg.usage = { ...ev.message.usage };\n } else if (ev.type === 'message_delta' && ev.usage) {\n msg.usage = { ...(msg.usage ?? {}), output_tokens: ev.usage.output_tokens };\n } else if (ev.type === 'content_block_start' && ev.content_block?.type === 'tool_use') {\n toolUseBlocks.push(ev.content_block);\n }\n },\n () => {\n msg.content = toolUseBlocks;\n const event = anthropicEventFrom(msg, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n },\n );\n }\n const event = anthropicEventFrom(result as AnthropicMessage, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n return result;\n };\n\n return new Proxy(client, {\n get(target, prop, recv) {\n if (prop === 'messages') {\n return new Proxy(messages, {\n get: (m, p, r) => (p === 'create' ? create : Reflect.get(m, p, r)),\n });\n }\n return Reflect.get(target, prop, recv);\n },\n });\n}\n\n// --- OpenAI (and OpenAI-compatible gateways) ------------------------------\n\ninterface OpenAIUsage {\n prompt_tokens?: number;\n completion_tokens?: number;\n completion_tokens_details?: { reasoning_tokens?: number };\n prompt_tokens_details?: { cached_tokens?: number };\n}\ninterface OpenAIToolCall {\n index?: number;\n}\ninterface OpenAIDelta {\n tool_calls?: OpenAIToolCall[];\n}\ninterface OpenAIChoice {\n message?: { tool_calls?: unknown[] };\n delta?: OpenAIDelta;\n}\ninterface OpenAICompletion {\n id?: string;\n model?: string;\n usage?: OpenAIUsage;\n choices?: OpenAIChoice[];\n}\n\n/** Map a non-streaming OpenAI chat completion to a track payload (or null). */\nexport function openAIEventFrom(\n completion: OpenAICompletion,\n opts: InstrumentOptions,\n): TrackEventInput | null {\n const usage = completion?.usage;\n if (!usage) return null;\n const runId = resolveRunId(opts, completion.id);\n const toolCalls = completion.choices?.[0]?.message?.tool_calls?.length ?? 0;\n const event: TrackEventInput = {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n provider: opts.provider ?? 'openai',\n model: completion.model ?? 'unknown',\n tokensIn: usage.prompt_tokens ?? 0,\n tokensOut: usage.completion_tokens ?? 0,\n reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? 0,\n cachedTokens: usage.prompt_tokens_details?.cached_tokens ?? 0,\n toolCalls,\n revenueUnitCents: resolveRevenue(opts, completion),\n idempotencyKey: completion.id ?? `${runId}#${randomId()}`,\n };\n return withCost(event, opts, completion);\n}\n\ninterface OpenAILike {\n chat: { completions: { create: (...args: never[]) => Promise<unknown> } };\n}\n\n/** Wrap an OpenAI (or OpenAI-compatible) client so `chat.completions.create`\n * auto-reports usage. Streaming works when the caller sets\n * `stream_options: { include_usage: true }` (required for OpenAI to emit a final\n * usage chunk); without it there are no token counts to report and the call is\n * passed through untouched. */\nexport function wrapOpenAI<T extends OpenAILike>(\n client: T,\n tollgate: TollgateClient,\n opts: InstrumentOptions,\n): T {\n const completions = client.chat.completions;\n const original = completions.create.bind(completions) as (...a: never[]) => Promise<unknown>;\n\n const create = async (...args: never[]): Promise<unknown> => {\n const result = await original(...args);\n if (isAsyncIterable(result)) {\n let id: string | undefined;\n let model: string | undefined;\n let usage: OpenAIUsage | undefined;\n const toolCallIndices = new Set<number>();\n return instrumentStream(\n result as AsyncIterable<OpenAICompletion>,\n (chunk) => {\n if (chunk.id) id = chunk.id;\n if (chunk.model) model = chunk.model;\n if (chunk.usage) usage = chunk.usage;\n for (const c of chunk.choices ?? []) {\n for (const tc of c.delta?.tool_calls ?? []) {\n if (tc.index !== undefined) toolCallIndices.add(tc.index);\n }\n }\n },\n () => {\n if (!usage) return;\n const synth: OpenAICompletion = { id, model, usage };\n if (toolCallIndices.size > 0) {\n synth.choices = [{ message: { tool_calls: new Array(toolCallIndices.size) } }];\n }\n const event = openAIEventFrom(synth, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n },\n );\n }\n const event = openAIEventFrom(result as OpenAICompletion, opts);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n return result;\n };\n\n return new Proxy(client, {\n get(target, prop, recv) {\n if (prop === 'chat') {\n const chat = Reflect.get(target, prop, recv) as OpenAILike['chat'];\n return new Proxy(chat, {\n get: (c, p, r) =>\n p === 'completions'\n ? new Proxy(completions, {\n get: (co, pp, rr) => (pp === 'create' ? create : Reflect.get(co, pp, rr)),\n })\n : Reflect.get(c, p, r),\n });\n }\n return Reflect.get(target, prop, recv);\n },\n });\n}\n\n// --- AWS Bedrock ----------------------------------------------------------\n// Bedrock's Converse API is command-based: the model id lives on the REQUEST,\n// not the response, so we read it from the command input. Usage is reported in\n// camelCase (inputTokens/outputTokens) plus optional cache token counts.\n\ninterface BedrockUsage {\n inputTokens?: number;\n outputTokens?: number;\n cacheReadInputTokens?: number;\n cacheWriteInputTokens?: number;\n}\ninterface BedrockContentBlock {\n toolUse?: unknown;\n}\ninterface BedrockConverseResponse {\n usage?: BedrockUsage;\n output?: { message?: { content?: BedrockContentBlock[] } };\n // ConverseStream returns an async-iterable `stream` of events; the final\n // `metadata` event carries the usage totals.\n stream?: AsyncIterable<{\n metadata?: { usage?: BedrockUsage };\n contentBlockStart?: { start?: { toolUse?: unknown } };\n }>;\n}\n\n/** Map a Bedrock Converse response (model from the request) to a track payload. */\nexport function bedrockEventFrom(\n usage: BedrockUsage | undefined,\n model: string,\n opts: InstrumentOptions,\n response: unknown = undefined,\n toolCalls = 0,\n): TrackEventInput | null {\n if (!usage) return null;\n const runId = resolveRunId(opts, undefined);\n const event: TrackEventInput = {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n provider: opts.provider ?? 'bedrock',\n model,\n tokensIn: usage.inputTokens ?? 0,\n tokensOut: usage.outputTokens ?? 0,\n cachedTokens: usage.cacheReadInputTokens ?? 0,\n cacheWrite5mTokens: usage.cacheWriteInputTokens ?? 0,\n toolCalls,\n revenueUnitCents: resolveRevenue(opts, response),\n idempotencyKey: `${runId}#${randomId()}`,\n };\n return withCost(event, opts, response);\n}\n\ninterface BedrockLike {\n send: (command: unknown, ...rest: never[]) => Promise<unknown>;\n}\n\n/** Wrap a Bedrock Runtime client so `send(ConverseCommand)` /\n * `send(ConverseStreamCommand)` auto-report usage. Non-Converse commands (no\n * usage in the response) pass through untouched. */\nexport function wrapBedrock<T extends BedrockLike>(\n client: T,\n tollgate: TollgateClient,\n opts: InstrumentOptions,\n): T {\n const originalSend = client.send.bind(client) as BedrockLike['send'];\n\n const send = async (command: unknown, ...rest: never[]): Promise<unknown> => {\n const result = (await originalSend(command, ...rest)) as BedrockConverseResponse;\n const model =\n ((command as { input?: { modelId?: string } })?.input?.modelId) ?? 'unknown';\n\n if (result?.stream && isAsyncIterable(result.stream)) {\n let usage: BedrockUsage | undefined;\n let streamToolCalls = 0;\n result.stream = instrumentStream(\n result.stream,\n (ev) => {\n if (ev.metadata?.usage) usage = ev.metadata.usage;\n if (ev.contentBlockStart?.start?.toolUse) streamToolCalls++;\n },\n () => {\n const event = bedrockEventFrom(usage, model, opts, result, streamToolCalls);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n },\n );\n return result;\n }\n\n if (result?.usage) {\n const tc = result.output?.message?.content?.filter((b) => b.toolUse != null).length ?? 0;\n const event = bedrockEventFrom(result.usage, model, opts, result, tc);\n if (event) fireAndForget(tollgate.track(event), opts.onError);\n }\n return result;\n };\n\n return new Proxy(client, {\n get(target, prop, recv) {\n if (prop === 'send') return send;\n return Reflect.get(target, prop, recv);\n },\n });\n}\n"]}
|