@tollgateai/sdk 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,12 @@ Track **real** LLM model usage and compute live gross margin with
4
4
  [Tollgate](https://tollgateai.vercel.app). The SDK reads the actual `usage`
5
5
  object off each provider response — you never hand-count tokens.
6
6
 
7
- Published on npm: [@tollgateai/sdk](https://www.npmjs.com/package/@tollgateai/sdk) (v0.1.2).
7
+ Published on npm: [@tollgateai/sdk](https://www.npmjs.com/package/@tollgateai/sdk) (v0.2.1).
8
+
9
+ Works with **OpenAI**, **Anthropic**, **AWS Bedrock**, and **every OpenAI-compatible
10
+ gateway** (Vercel AI Gateway, OpenRouter, Groq, Together, Nebius, local vLLM, …) —
11
+ streaming and non-streaming. Cost is computed server-side from the token counts the
12
+ wrappers capture, so no provider has to return a dollar figure.
8
13
 
9
14
  ```bash
10
15
  npm install @tollgateai/sdk
@@ -81,9 +86,71 @@ await openai.chat.completions.create({
81
86
  `revenueUnitCents` may also be a function of the response, e.g.
82
87
  `revenueUnitCents: (res) => res.someField ? 50 : 0`.
83
88
 
89
+ ### OpenAI-compatible gateways
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:
93
+
94
+ ```ts
95
+ const openai = new OpenAI({ apiKey: process.env.GROQ_API_KEY, baseURL: 'https://api.groq.com/openai/v1' });
96
+ const client = wrapOpenAI(openai, tollgate, {
97
+ customerId: 'cust_A',
98
+ provider: 'openai_compatible', // Groq / OpenRouter / Together / Nebius / vLLM …
99
+ });
100
+ await client.chat.completions.create({ model: 'llama-3.3-70b-versatile', messages: [...] });
101
+ ```
102
+
103
+ ### Streaming
104
+
105
+ Streaming is captured automatically. For **OpenAI / compatible**, pass
106
+ `stream_options: { include_usage: true }` (required for a final usage chunk); for
107
+ **Anthropic** no flag is needed. Just iterate the stream as usual:
108
+
109
+ ```ts
110
+ const stream = await client.chat.completions.create({
111
+ model: 'gpt-4o', stream: true, stream_options: { include_usage: true },
112
+ messages: [{ role: 'user', content: 'Hello' }],
113
+ });
114
+ for await (const chunk of stream) { /* … */ } // usage is reported when the stream ends
115
+ ```
116
+
117
+ ### AWS Bedrock
118
+
119
+ Wrap a `BedrockRuntimeClient` so `ConverseCommand` / `ConverseStreamCommand`
120
+ auto-report usage (the model id is read from the command):
121
+
122
+ ```ts
123
+ import { BedrockRuntimeClient, ConverseCommand } from '@aws-sdk/client-bedrock-runtime';
124
+ import { wrapBedrock } from '@tollgateai/sdk';
125
+
126
+ const bedrock = wrapBedrock(new BedrockRuntimeClient({ region: 'us-east-1' }), tollgate, { customerId: 'cust_A' });
127
+ await bedrock.send(new ConverseCommand({ modelId: 'anthropic.claude-3-5-sonnet-20241022-v2:0', messages: [...] }));
128
+ ```
129
+
130
+ ### Already have an exact cost?
131
+
132
+ Pass `providerCostCents` (a number or a function of the response) and the server
133
+ uses it verbatim, skipping the rate card entirely.
134
+
135
+ ## Set up customers & plans in code
136
+
137
+ Create a customer and assign its plan **before** sending usage, so plan-priced
138
+ revenue (especially `usage_based`, which is computed at ingest) is recognized from
139
+ the first event. Idempotent — safe to run on every boot.
140
+
141
+ ```ts
142
+ await tollgate.upsertCustomer({
143
+ customerId: 'cust_A',
144
+ name: 'Acme',
145
+ seats: 5,
146
+ // assign an existing plan by id, or declare one inline (upserted by name):
147
+ plan: { name: 'Usage', pricingModel: 'usage_based', unitRevenueCents: 10 },
148
+ });
149
+ ```
150
+
84
151
  ## Manual tracking
85
152
 
86
- For providers without a wrapper (Bedrock, custom gateways) or full control:
153
+ For full control or unusual providers:
87
154
 
88
155
  ```ts
89
156
  import { createTollgateClient } from '@tollgateai/sdk';
@@ -109,8 +176,10 @@ await tollgate.track({
109
176
  - **Idempotent.** Events are deduplicated on `idempotencyKey` (auto-set to the
110
177
  provider response id by the wrappers), so retries never double-count.
111
178
  - **No prompt content is ever sent** — only token counts and metadata.
112
- - **Streaming** responses are not auto-tracked yet (the wrappers only report when
113
- a non-streaming `usage` object is present). Track those manually for now.
179
+ - **Streaming is auto-tracked** (OpenAI needs `stream_options.include_usage`).
180
+ - **Cost from tokens.** The server prices every event from token counts × a rate
181
+ card that auto-syncs daily from the public LiteLLM registry — unknown models are
182
+ priced at $0 and flagged in logs. See [docs/PRICING.md](../../docs/PRICING.md).
114
183
  - **Non-blocking.** Auto-instrumented tracking runs in the background; failures
115
184
  are passed to `onError` (default `console.warn`) and never break your call.
116
185
 
@@ -120,8 +189,12 @@ await tollgate.track({
120
189
  - `resolve({ runId, customerId, outcome, revenueUnitCents? })` → close a run with
121
190
  its outcome; books revenue once, only when `outcome` is `'resolved'`
122
191
  - `wrapAnthropic(client, tollgate, options)` → instrumented Anthropic client
123
- - `wrapOpenAI(client, tollgate, options)` → instrumented OpenAI client
124
- - `anthropicEventFrom(msg, options)` / `openAIEventFrom(completion, options)`
125
- build a track payload manually from a provider response
192
+ - `wrapOpenAI(client, tollgate, options)` → instrumented OpenAI / compatible client
193
+ - `wrapBedrock(client, tollgate, options)` instrumented Bedrock Runtime client
194
+ - `anthropicEventFrom` / `openAIEventFrom` / `bedrockEventFrom` → build a track
195
+ payload manually from a provider response
196
+
197
+ `options` accepts `customerId`, `agentId`, `runId`, `revenueUnitCents`,
198
+ `provider` (override; e.g. `'openai_compatible'`), `providerCostCents`, and `onError`.
126
199
 
127
200
  Licensed for use with Tollgate. Not open source.
package/dist/index.cjs CHANGED
@@ -23,7 +23,7 @@ function createTollgateClient(opts = {}) {
23
23
  if (typeof doFetch !== "function") {
24
24
  throw new TollgateError("No fetch implementation available \u2014 pass `fetch` in options.");
25
25
  }
26
- async function track(event) {
26
+ async function postJson(path, body) {
27
27
  if (!apiKey) {
28
28
  throw new TollgateError("Missing API key \u2014 set opts.apiKey or TOLLGATE_API_KEY.");
29
29
  }
@@ -32,23 +32,23 @@ function createTollgateClient(opts = {}) {
32
32
  const controller = new AbortController();
33
33
  const timer = setTimeout(() => controller.abort(), timeoutMs);
34
34
  try {
35
- const res = await doFetch(`${baseUrl}/api/track`, {
35
+ const res = await doFetch(`${baseUrl}${path}`, {
36
36
  method: "POST",
37
37
  headers: {
38
38
  "Content-Type": "application/json",
39
39
  Authorization: `Bearer ${apiKey}`
40
40
  },
41
- body: JSON.stringify(event),
41
+ body: JSON.stringify(body),
42
42
  signal: controller.signal
43
43
  });
44
44
  if (res.ok) {
45
45
  return await res.json();
46
46
  }
47
47
  if (res.status >= 500 || res.status === 429) {
48
- lastErr = new TollgateError(`Tollgate track failed (${res.status})`, res.status);
48
+ lastErr = new TollgateError(`Tollgate request failed (${res.status})`, res.status);
49
49
  } else {
50
- const body = await res.json().catch(() => ({}));
51
- throw new TollgateError(`Tollgate track failed (${res.status})`, res.status, body);
50
+ const errBody = await res.json().catch(() => ({}));
51
+ throw new TollgateError(`Tollgate request failed (${res.status})`, res.status, errBody);
52
52
  }
53
53
  } catch (err) {
54
54
  if (err instanceof TollgateError && err.status && err.status < 500 && err.status !== 429) {
@@ -62,7 +62,13 @@ function createTollgateClient(opts = {}) {
62
62
  await sleep(2 ** attempt * 200);
63
63
  }
64
64
  }
65
- throw lastErr instanceof Error ? lastErr : new TollgateError("Tollgate track failed after retries");
65
+ throw lastErr instanceof Error ? lastErr : new TollgateError("Tollgate request failed after retries");
66
+ }
67
+ function track(event) {
68
+ return postJson("/api/track", event);
69
+ }
70
+ function upsertCustomer(input) {
71
+ return postJson("/api/sdk/customer", input);
66
72
  }
67
73
  function resolve(input) {
68
74
  return track({
@@ -80,7 +86,7 @@ function createTollgateClient(opts = {}) {
80
86
  ts: input.ts
81
87
  });
82
88
  }
83
- return { track, resolve };
89
+ return { track, resolve, upsertCustomer };
84
90
  }
85
91
 
86
92
  // src/instrument.ts
@@ -96,9 +102,58 @@ function resolveRunId(opts, responseId) {
96
102
  function resolveRevenue(opts, response) {
97
103
  return typeof opts.revenueUnitCents === "function" ? opts.revenueUnitCents(response) : opts.revenueUnitCents;
98
104
  }
105
+ function resolveCost(opts, response) {
106
+ return typeof opts.providerCostCents === "function" ? opts.providerCostCents(response) : opts.providerCostCents;
107
+ }
108
+ function withCost(event, opts, response) {
109
+ const cost = resolveCost(opts, response);
110
+ if (cost !== void 0) event.providerCostCents = cost;
111
+ return event;
112
+ }
99
113
  function fireAndForget(p, onError) {
100
114
  p.catch((err) => (onError ?? ((e) => console.warn("[tollgate] track failed:", e)))(err));
101
115
  }
116
+ function isAsyncIterable(x) {
117
+ return x != null && typeof x[Symbol.asyncIterator] === "function";
118
+ }
119
+ function instrumentStream(stream, onChunk, onDone) {
120
+ let finished = false;
121
+ const finish = () => {
122
+ if (finished) return;
123
+ finished = true;
124
+ onDone();
125
+ };
126
+ return new Proxy(stream, {
127
+ get(target, prop, recv) {
128
+ if (prop === Symbol.asyncIterator) {
129
+ return function instrumentedIterator() {
130
+ const inner = target[Symbol.asyncIterator]();
131
+ return {
132
+ async next(...a) {
133
+ const r = await inner.next(...a);
134
+ if (r.done) finish();
135
+ else onChunk(r.value);
136
+ return r;
137
+ },
138
+ async return(v) {
139
+ finish();
140
+ return inner.return ? inner.return(v) : { done: true, value: v };
141
+ },
142
+ async throw(e) {
143
+ finish();
144
+ if (inner.throw) return inner.throw(e);
145
+ throw e;
146
+ },
147
+ [Symbol.asyncIterator]() {
148
+ return this;
149
+ }
150
+ };
151
+ };
152
+ }
153
+ return Reflect.get(target, prop, recv);
154
+ }
155
+ });
156
+ }
102
157
  function anthropicEventFrom(msg, opts) {
103
158
  const usage = msg?.usage;
104
159
  if (!usage) return null;
@@ -106,11 +161,11 @@ function anthropicEventFrom(msg, opts) {
106
161
  const fivem = usage.cache_creation?.ephemeral_5m_input_tokens;
107
162
  const oneh = usage.cache_creation?.ephemeral_1h_input_tokens;
108
163
  const hasSplit = fivem !== void 0 || oneh !== void 0;
109
- return {
164
+ const event = {
110
165
  customerId: opts.customerId,
111
166
  agentId: opts.agentId,
112
167
  runId,
113
- provider: "anthropic",
168
+ provider: opts.provider ?? "anthropic",
114
169
  model: msg.model ?? "unknown",
115
170
  tokensIn: usage.input_tokens ?? 0,
116
171
  tokensOut: usage.output_tokens ?? 0,
@@ -120,12 +175,32 @@ function anthropicEventFrom(msg, opts) {
120
175
  revenueUnitCents: resolveRevenue(opts, msg),
121
176
  idempotencyKey: msg.id ?? `${runId}#${randomId()}`
122
177
  };
178
+ return withCost(event, opts, msg);
123
179
  }
124
180
  function wrapAnthropic(client, tollgate, opts) {
125
181
  const messages = client.messages;
126
182
  const original = messages.create.bind(messages);
127
183
  const create = async (...args) => {
128
184
  const result = await original(...args);
185
+ if (isAsyncIterable(result)) {
186
+ const msg = {};
187
+ return instrumentStream(
188
+ result,
189
+ (ev) => {
190
+ if (ev.type === "message_start" && ev.message) {
191
+ msg.id = ev.message.id;
192
+ msg.model = ev.message.model;
193
+ msg.usage = { ...ev.message.usage };
194
+ } else if (ev.type === "message_delta" && ev.usage) {
195
+ msg.usage = { ...msg.usage ?? {}, output_tokens: ev.usage.output_tokens };
196
+ }
197
+ },
198
+ () => {
199
+ const event2 = anthropicEventFrom(msg, opts);
200
+ if (event2) fireAndForget(tollgate.track(event2), opts.onError);
201
+ }
202
+ );
203
+ }
129
204
  const event = anthropicEventFrom(result, opts);
130
205
  if (event) fireAndForget(tollgate.track(event), opts.onError);
131
206
  return result;
@@ -145,11 +220,11 @@ function openAIEventFrom(completion, opts) {
145
220
  const usage = completion?.usage;
146
221
  if (!usage) return null;
147
222
  const runId = resolveRunId(opts, completion.id);
148
- return {
223
+ const event = {
149
224
  customerId: opts.customerId,
150
225
  agentId: opts.agentId,
151
226
  runId,
152
- provider: "openai",
227
+ provider: opts.provider ?? "openai",
153
228
  model: completion.model ?? "unknown",
154
229
  tokensIn: usage.prompt_tokens ?? 0,
155
230
  tokensOut: usage.completion_tokens ?? 0,
@@ -158,12 +233,31 @@ function openAIEventFrom(completion, opts) {
158
233
  revenueUnitCents: resolveRevenue(opts, completion),
159
234
  idempotencyKey: completion.id ?? `${runId}#${randomId()}`
160
235
  };
236
+ return withCost(event, opts, completion);
161
237
  }
162
238
  function wrapOpenAI(client, tollgate, opts) {
163
239
  const completions = client.chat.completions;
164
240
  const original = completions.create.bind(completions);
165
241
  const create = async (...args) => {
166
242
  const result = await original(...args);
243
+ if (isAsyncIterable(result)) {
244
+ let id;
245
+ let model;
246
+ let usage;
247
+ return instrumentStream(
248
+ result,
249
+ (chunk) => {
250
+ if (chunk.id) id = chunk.id;
251
+ if (chunk.model) model = chunk.model;
252
+ if (chunk.usage) usage = chunk.usage;
253
+ },
254
+ () => {
255
+ if (!usage) return;
256
+ const event2 = openAIEventFrom({ id, model, usage }, opts);
257
+ if (event2) fireAndForget(tollgate.track(event2), opts.onError);
258
+ }
259
+ );
260
+ }
167
261
  const event = openAIEventFrom(result, opts);
168
262
  if (event) fireAndForget(tollgate.track(event), opts.onError);
169
263
  return result;
@@ -182,12 +276,64 @@ function wrapOpenAI(client, tollgate, opts) {
182
276
  }
183
277
  });
184
278
  }
279
+ function bedrockEventFrom(usage, model, opts, response = void 0) {
280
+ if (!usage) return null;
281
+ const runId = resolveRunId(opts, void 0);
282
+ const event = {
283
+ customerId: opts.customerId,
284
+ agentId: opts.agentId,
285
+ runId,
286
+ provider: opts.provider ?? "bedrock",
287
+ model,
288
+ tokensIn: usage.inputTokens ?? 0,
289
+ tokensOut: usage.outputTokens ?? 0,
290
+ cachedTokens: usage.cacheReadInputTokens ?? 0,
291
+ cacheWrite5mTokens: usage.cacheWriteInputTokens ?? 0,
292
+ revenueUnitCents: resolveRevenue(opts, response),
293
+ idempotencyKey: `${runId}#${randomId()}`
294
+ };
295
+ return withCost(event, opts, response);
296
+ }
297
+ function wrapBedrock(client, tollgate, opts) {
298
+ const originalSend = client.send.bind(client);
299
+ const send = async (command, ...rest) => {
300
+ const result = await originalSend(command, ...rest);
301
+ const model = command?.input?.modelId ?? "unknown";
302
+ if (result?.stream && isAsyncIterable(result.stream)) {
303
+ let usage;
304
+ result.stream = instrumentStream(
305
+ result.stream,
306
+ (ev) => {
307
+ if (ev.metadata?.usage) usage = ev.metadata.usage;
308
+ },
309
+ () => {
310
+ const event = bedrockEventFrom(usage, model, opts, result);
311
+ if (event) fireAndForget(tollgate.track(event), opts.onError);
312
+ }
313
+ );
314
+ return result;
315
+ }
316
+ if (result?.usage) {
317
+ const event = bedrockEventFrom(result.usage, model, opts, result);
318
+ if (event) fireAndForget(tollgate.track(event), opts.onError);
319
+ }
320
+ return result;
321
+ };
322
+ return new Proxy(client, {
323
+ get(target, prop, recv) {
324
+ if (prop === "send") return send;
325
+ return Reflect.get(target, prop, recv);
326
+ }
327
+ });
328
+ }
185
329
 
186
330
  exports.TollgateError = TollgateError;
187
331
  exports.anthropicEventFrom = anthropicEventFrom;
332
+ exports.bedrockEventFrom = bedrockEventFrom;
188
333
  exports.createTollgateClient = createTollgateClient;
189
334
  exports.openAIEventFrom = openAIEventFrom;
190
335
  exports.wrapAnthropic = wrapAnthropic;
336
+ exports.wrapBedrock = wrapBedrock;
191
337
  exports.wrapOpenAI = wrapOpenAI;
192
338
  //# sourceMappingURL=index.cjs.map
193
339
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/instrument.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Provider, RunOutcome, TrackEventInput, TrackResult } 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}\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 async function track(event: TrackEventInput): Promise<TrackResult> {\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}/api/track`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(event),\n signal: controller.signal,\n });\n\n // 200 = idempotent duplicate, 201 = created. Both are success.\n if (res.ok) {\n return (await res.json()) as TrackResult;\n }\n\n // Retry transient failures; fail fast on 4xx (except 429).\n if (res.status >= 500 || res.status === 429) {\n lastErr = new TollgateError(`Tollgate track failed (${res.status})`, res.status);\n } else {\n const body = await res.json().catch(() => ({}));\n throw new TollgateError(`Tollgate track failed (${res.status})`, res.status, body);\n }\n } catch (err) {\n // Don't retry deterministic client errors.\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 track failed after retries');\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 };\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\nimport type { TollgateClient } from './client';\nimport type { 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 /** Revenue per call in cents (or a function of the response). */\n revenueUnitCents?: 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 fireAndForget(p: Promise<unknown>, onError?: InstrumentOptions['onError']): void {\n p.catch((err) => (onError ?? ((e) => console.warn('[tollgate] track failed:', e)))(err));\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\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 return {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n 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}\n\ninterface AnthropicLike {\n messages: { create: (...args: never[]) => Promise<unknown> };\n}\n\n/** Wrap an Anthropic client so `messages.create` auto-reports usage. */\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 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 ---------------------------------------------------------------\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 return {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n 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}\n\ninterface OpenAILike {\n chat: { completions: { create: (...args: never[]) => Promise<unknown> } };\n}\n\n/** Wrap an OpenAI client so `chat.completions.create` auto-reports usage. */\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 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"]}
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"]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- type Provider = 'anthropic' | 'openai' | 'bedrock';
1
+ type Provider = 'anthropic' | 'openai' | 'openai_compatible' | 'bedrock';
2
2
  type EventType = 'llm' | 'tool' | 'retrieval';
3
3
  /** Terminal outcome of a run, reported on its closing event (see `resolve()`).
4
4
  * Under outcome-based pricing only a `resolved` run books revenue — an
@@ -33,6 +33,9 @@ interface TrackEventInput {
33
33
  outcome?: RunOutcome;
34
34
  /** Per-event revenue contribution in cents (e.g. 50 for a $0.50 unit). */
35
35
  revenueUnitCents?: number;
36
+ /** Provider/gateway-reported cost in cents. When present and > 0 the server
37
+ * uses it verbatim and skips the rate card (fractional cents accepted). */
38
+ providerCostCents?: number;
36
39
  /** Required for exactly-once ingestion (e.g. "run_12345#step_1"). */
37
40
  idempotencyKey: string;
38
41
  /** ISO timestamp; defaults to server receive time. */
@@ -42,6 +45,38 @@ interface TrackResult {
42
45
  status: 'created' | 'duplicate' | string;
43
46
  eventId: string;
44
47
  }
48
+ /** Revenue model for a plan (mirrors the server). */
49
+ type PricingModel = 'per_unit' | 'per_resolution' | 'usage_based' | 'per_seat' | 'flat' | 'hybrid';
50
+ /** Declare a plan inline (upserted by name) when setting up a customer. */
51
+ interface PlanInput {
52
+ name: string;
53
+ pricingModel: PricingModel;
54
+ /** Per-unit / per-1k-token / per-seat amount in cents (meaning depends on model). */
55
+ unitRevenueCents?: number;
56
+ /** Flat/subscription base in cents per month (flat & hybrid). */
57
+ baseRevenueCents?: number;
58
+ }
59
+ /** Input for `upsertCustomer` — create/update a customer and (optionally) its plan. */
60
+ interface UpsertCustomerInput {
61
+ /** Your end customer's stable external id (same value you pass to track). */
62
+ customerId: string;
63
+ name?: string;
64
+ company?: string;
65
+ useCase?: string;
66
+ /** Seat count for per_seat plans. */
67
+ seats?: number;
68
+ /** Assign an existing plan by id … */
69
+ planId?: string;
70
+ /** … or declare one inline (upserted by name). */
71
+ plan?: PlanInput;
72
+ }
73
+ interface UpsertCustomerResult {
74
+ status: string;
75
+ customerId: string;
76
+ /** Account-namespaced internal id. */
77
+ id: string;
78
+ planId: string | null;
79
+ }
45
80
 
46
81
  interface TollgateClientOptions {
47
82
  /** Account API key (`tg_live_…`). Falls back to `process.env.TOLLGATE_API_KEY`. */
@@ -84,6 +119,10 @@ interface TollgateClient {
84
119
  /** Close a run with its outcome — book revenue once, only if resolved.
85
120
  * Convenience over `track()`: sends a zero-usage terminal event. */
86
121
  resolve(input: ResolveInput): Promise<TrackResult>;
122
+ /** Create/update a customer and (optionally) its plan, in code. Call this
123
+ * BEFORE sending usage so plan-priced revenue (esp. usage_based, which is
124
+ * computed at ingest) is recognized from the first event. Idempotent. */
125
+ upsertCustomer(input: UpsertCustomerInput): Promise<UpsertCustomerResult>;
87
126
  }
88
127
  declare function createTollgateClient(opts?: TollgateClientOptions): TollgateClient;
89
128
 
@@ -92,8 +131,17 @@ interface InstrumentOptions {
92
131
  customerId: string;
93
132
  /** Optional agent/workflow id. */
94
133
  agentId?: string;
134
+ /** Override the reported provider. Defaults per wrapper ('openai' /
135
+ * 'anthropic' / 'bedrock'). Set to 'openai_compatible' when the client points
136
+ * at an OpenAI-shaped gateway (Vercel AI Gateway, OpenRouter, Groq, Together,
137
+ * Nebius, local vLLM, …) so the server prices it by the gateway-echoed model. */
138
+ provider?: Provider;
95
139
  /** Revenue per call in cents (or a function of the response). */
96
140
  revenueUnitCents?: number | ((response: unknown) => number | undefined);
141
+ /** Provider/gateway-reported cost in cents (or a function of the response).
142
+ * When present and > 0, the server uses it verbatim and skips the rate card —
143
+ * the authoritative escape hatch when you already have an exact cost. */
144
+ providerCostCents?: number | ((response: unknown) => number | undefined);
97
145
  /** Override the run id; defaults to the provider response id. */
98
146
  runId?: string | (() => string);
99
147
  /** Called if a background track() fails. Defaults to console.warn. */
@@ -121,7 +169,8 @@ interface AnthropicLike {
121
169
  create: (...args: never[]) => Promise<unknown>;
122
170
  };
123
171
  }
124
- /** Wrap an Anthropic client so `messages.create` auto-reports usage. */
172
+ /** Wrap an Anthropic client so `messages.create` auto-reports usage (streaming
173
+ * and non-streaming). */
125
174
  declare function wrapAnthropic<T extends AnthropicLike>(client: T, tollgate: TollgateClient, opts: InstrumentOptions): T;
126
175
  interface OpenAIUsage {
127
176
  prompt_tokens?: number;
@@ -147,7 +196,26 @@ interface OpenAILike {
147
196
  };
148
197
  };
149
198
  }
150
- /** Wrap an OpenAI client so `chat.completions.create` auto-reports usage. */
199
+ /** Wrap an OpenAI (or OpenAI-compatible) client so `chat.completions.create`
200
+ * auto-reports usage. Streaming works when the caller sets
201
+ * `stream_options: { include_usage: true }` (required for OpenAI to emit a final
202
+ * usage chunk); without it there are no token counts to report and the call is
203
+ * passed through untouched. */
151
204
  declare function wrapOpenAI<T extends OpenAILike>(client: T, tollgate: TollgateClient, opts: InstrumentOptions): T;
205
+ interface BedrockUsage {
206
+ inputTokens?: number;
207
+ outputTokens?: number;
208
+ cacheReadInputTokens?: number;
209
+ cacheWriteInputTokens?: number;
210
+ }
211
+ /** 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;
213
+ interface BedrockLike {
214
+ send: (command: unknown, ...rest: never[]) => Promise<unknown>;
215
+ }
216
+ /** Wrap a Bedrock Runtime client so `send(ConverseCommand)` /
217
+ * `send(ConverseStreamCommand)` auto-report usage. Non-Converse commands (no
218
+ * usage in the response) pass through untouched. */
219
+ declare function wrapBedrock<T extends BedrockLike>(client: T, tollgate: TollgateClient, opts: InstrumentOptions): T;
152
220
 
153
- export { type EventType, type InstrumentOptions, type Provider, type ResolveInput, type RunOutcome, type TollgateClient, type TollgateClientOptions, TollgateError, type TrackEventInput, type TrackResult, anthropicEventFrom, createTollgateClient, openAIEventFrom, wrapAnthropic, wrapOpenAI };
221
+ export { type EventType, type InstrumentOptions, type PlanInput, type PricingModel, type Provider, type ResolveInput, type RunOutcome, type TollgateClient, type TollgateClientOptions, TollgateError, type TrackEventInput, type TrackResult, type UpsertCustomerInput, type UpsertCustomerResult, anthropicEventFrom, bedrockEventFrom, createTollgateClient, openAIEventFrom, wrapAnthropic, wrapBedrock, wrapOpenAI };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- type Provider = 'anthropic' | 'openai' | 'bedrock';
1
+ type Provider = 'anthropic' | 'openai' | 'openai_compatible' | 'bedrock';
2
2
  type EventType = 'llm' | 'tool' | 'retrieval';
3
3
  /** Terminal outcome of a run, reported on its closing event (see `resolve()`).
4
4
  * Under outcome-based pricing only a `resolved` run books revenue — an
@@ -33,6 +33,9 @@ interface TrackEventInput {
33
33
  outcome?: RunOutcome;
34
34
  /** Per-event revenue contribution in cents (e.g. 50 for a $0.50 unit). */
35
35
  revenueUnitCents?: number;
36
+ /** Provider/gateway-reported cost in cents. When present and > 0 the server
37
+ * uses it verbatim and skips the rate card (fractional cents accepted). */
38
+ providerCostCents?: number;
36
39
  /** Required for exactly-once ingestion (e.g. "run_12345#step_1"). */
37
40
  idempotencyKey: string;
38
41
  /** ISO timestamp; defaults to server receive time. */
@@ -42,6 +45,38 @@ interface TrackResult {
42
45
  status: 'created' | 'duplicate' | string;
43
46
  eventId: string;
44
47
  }
48
+ /** Revenue model for a plan (mirrors the server). */
49
+ type PricingModel = 'per_unit' | 'per_resolution' | 'usage_based' | 'per_seat' | 'flat' | 'hybrid';
50
+ /** Declare a plan inline (upserted by name) when setting up a customer. */
51
+ interface PlanInput {
52
+ name: string;
53
+ pricingModel: PricingModel;
54
+ /** Per-unit / per-1k-token / per-seat amount in cents (meaning depends on model). */
55
+ unitRevenueCents?: number;
56
+ /** Flat/subscription base in cents per month (flat & hybrid). */
57
+ baseRevenueCents?: number;
58
+ }
59
+ /** Input for `upsertCustomer` — create/update a customer and (optionally) its plan. */
60
+ interface UpsertCustomerInput {
61
+ /** Your end customer's stable external id (same value you pass to track). */
62
+ customerId: string;
63
+ name?: string;
64
+ company?: string;
65
+ useCase?: string;
66
+ /** Seat count for per_seat plans. */
67
+ seats?: number;
68
+ /** Assign an existing plan by id … */
69
+ planId?: string;
70
+ /** … or declare one inline (upserted by name). */
71
+ plan?: PlanInput;
72
+ }
73
+ interface UpsertCustomerResult {
74
+ status: string;
75
+ customerId: string;
76
+ /** Account-namespaced internal id. */
77
+ id: string;
78
+ planId: string | null;
79
+ }
45
80
 
46
81
  interface TollgateClientOptions {
47
82
  /** Account API key (`tg_live_…`). Falls back to `process.env.TOLLGATE_API_KEY`. */
@@ -84,6 +119,10 @@ interface TollgateClient {
84
119
  /** Close a run with its outcome — book revenue once, only if resolved.
85
120
  * Convenience over `track()`: sends a zero-usage terminal event. */
86
121
  resolve(input: ResolveInput): Promise<TrackResult>;
122
+ /** Create/update a customer and (optionally) its plan, in code. Call this
123
+ * BEFORE sending usage so plan-priced revenue (esp. usage_based, which is
124
+ * computed at ingest) is recognized from the first event. Idempotent. */
125
+ upsertCustomer(input: UpsertCustomerInput): Promise<UpsertCustomerResult>;
87
126
  }
88
127
  declare function createTollgateClient(opts?: TollgateClientOptions): TollgateClient;
89
128
 
@@ -92,8 +131,17 @@ interface InstrumentOptions {
92
131
  customerId: string;
93
132
  /** Optional agent/workflow id. */
94
133
  agentId?: string;
134
+ /** Override the reported provider. Defaults per wrapper ('openai' /
135
+ * 'anthropic' / 'bedrock'). Set to 'openai_compatible' when the client points
136
+ * at an OpenAI-shaped gateway (Vercel AI Gateway, OpenRouter, Groq, Together,
137
+ * Nebius, local vLLM, …) so the server prices it by the gateway-echoed model. */
138
+ provider?: Provider;
95
139
  /** Revenue per call in cents (or a function of the response). */
96
140
  revenueUnitCents?: number | ((response: unknown) => number | undefined);
141
+ /** Provider/gateway-reported cost in cents (or a function of the response).
142
+ * When present and > 0, the server uses it verbatim and skips the rate card —
143
+ * the authoritative escape hatch when you already have an exact cost. */
144
+ providerCostCents?: number | ((response: unknown) => number | undefined);
97
145
  /** Override the run id; defaults to the provider response id. */
98
146
  runId?: string | (() => string);
99
147
  /** Called if a background track() fails. Defaults to console.warn. */
@@ -121,7 +169,8 @@ interface AnthropicLike {
121
169
  create: (...args: never[]) => Promise<unknown>;
122
170
  };
123
171
  }
124
- /** Wrap an Anthropic client so `messages.create` auto-reports usage. */
172
+ /** Wrap an Anthropic client so `messages.create` auto-reports usage (streaming
173
+ * and non-streaming). */
125
174
  declare function wrapAnthropic<T extends AnthropicLike>(client: T, tollgate: TollgateClient, opts: InstrumentOptions): T;
126
175
  interface OpenAIUsage {
127
176
  prompt_tokens?: number;
@@ -147,7 +196,26 @@ interface OpenAILike {
147
196
  };
148
197
  };
149
198
  }
150
- /** Wrap an OpenAI client so `chat.completions.create` auto-reports usage. */
199
+ /** Wrap an OpenAI (or OpenAI-compatible) client so `chat.completions.create`
200
+ * auto-reports usage. Streaming works when the caller sets
201
+ * `stream_options: { include_usage: true }` (required for OpenAI to emit a final
202
+ * usage chunk); without it there are no token counts to report and the call is
203
+ * passed through untouched. */
151
204
  declare function wrapOpenAI<T extends OpenAILike>(client: T, tollgate: TollgateClient, opts: InstrumentOptions): T;
205
+ interface BedrockUsage {
206
+ inputTokens?: number;
207
+ outputTokens?: number;
208
+ cacheReadInputTokens?: number;
209
+ cacheWriteInputTokens?: number;
210
+ }
211
+ /** 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;
213
+ interface BedrockLike {
214
+ send: (command: unknown, ...rest: never[]) => Promise<unknown>;
215
+ }
216
+ /** Wrap a Bedrock Runtime client so `send(ConverseCommand)` /
217
+ * `send(ConverseStreamCommand)` auto-report usage. Non-Converse commands (no
218
+ * usage in the response) pass through untouched. */
219
+ declare function wrapBedrock<T extends BedrockLike>(client: T, tollgate: TollgateClient, opts: InstrumentOptions): T;
152
220
 
153
- export { type EventType, type InstrumentOptions, type Provider, type ResolveInput, type RunOutcome, type TollgateClient, type TollgateClientOptions, TollgateError, type TrackEventInput, type TrackResult, anthropicEventFrom, createTollgateClient, openAIEventFrom, wrapAnthropic, wrapOpenAI };
221
+ export { type EventType, type InstrumentOptions, type PlanInput, type PricingModel, type Provider, type ResolveInput, type RunOutcome, type TollgateClient, type TollgateClientOptions, TollgateError, type TrackEventInput, type TrackResult, type UpsertCustomerInput, type UpsertCustomerResult, anthropicEventFrom, bedrockEventFrom, createTollgateClient, openAIEventFrom, wrapAnthropic, wrapBedrock, wrapOpenAI };
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ function createTollgateClient(opts = {}) {
21
21
  if (typeof doFetch !== "function") {
22
22
  throw new TollgateError("No fetch implementation available \u2014 pass `fetch` in options.");
23
23
  }
24
- async function track(event) {
24
+ async function postJson(path, body) {
25
25
  if (!apiKey) {
26
26
  throw new TollgateError("Missing API key \u2014 set opts.apiKey or TOLLGATE_API_KEY.");
27
27
  }
@@ -30,23 +30,23 @@ function createTollgateClient(opts = {}) {
30
30
  const controller = new AbortController();
31
31
  const timer = setTimeout(() => controller.abort(), timeoutMs);
32
32
  try {
33
- const res = await doFetch(`${baseUrl}/api/track`, {
33
+ const res = await doFetch(`${baseUrl}${path}`, {
34
34
  method: "POST",
35
35
  headers: {
36
36
  "Content-Type": "application/json",
37
37
  Authorization: `Bearer ${apiKey}`
38
38
  },
39
- body: JSON.stringify(event),
39
+ body: JSON.stringify(body),
40
40
  signal: controller.signal
41
41
  });
42
42
  if (res.ok) {
43
43
  return await res.json();
44
44
  }
45
45
  if (res.status >= 500 || res.status === 429) {
46
- lastErr = new TollgateError(`Tollgate track failed (${res.status})`, res.status);
46
+ lastErr = new TollgateError(`Tollgate request failed (${res.status})`, res.status);
47
47
  } else {
48
- const body = await res.json().catch(() => ({}));
49
- throw new TollgateError(`Tollgate track failed (${res.status})`, res.status, body);
48
+ const errBody = await res.json().catch(() => ({}));
49
+ throw new TollgateError(`Tollgate request failed (${res.status})`, res.status, errBody);
50
50
  }
51
51
  } catch (err) {
52
52
  if (err instanceof TollgateError && err.status && err.status < 500 && err.status !== 429) {
@@ -60,7 +60,13 @@ function createTollgateClient(opts = {}) {
60
60
  await sleep(2 ** attempt * 200);
61
61
  }
62
62
  }
63
- throw lastErr instanceof Error ? lastErr : new TollgateError("Tollgate track failed after retries");
63
+ throw lastErr instanceof Error ? lastErr : new TollgateError("Tollgate request failed after retries");
64
+ }
65
+ function track(event) {
66
+ return postJson("/api/track", event);
67
+ }
68
+ function upsertCustomer(input) {
69
+ return postJson("/api/sdk/customer", input);
64
70
  }
65
71
  function resolve(input) {
66
72
  return track({
@@ -78,7 +84,7 @@ function createTollgateClient(opts = {}) {
78
84
  ts: input.ts
79
85
  });
80
86
  }
81
- return { track, resolve };
87
+ return { track, resolve, upsertCustomer };
82
88
  }
83
89
 
84
90
  // src/instrument.ts
@@ -94,9 +100,58 @@ function resolveRunId(opts, responseId) {
94
100
  function resolveRevenue(opts, response) {
95
101
  return typeof opts.revenueUnitCents === "function" ? opts.revenueUnitCents(response) : opts.revenueUnitCents;
96
102
  }
103
+ function resolveCost(opts, response) {
104
+ return typeof opts.providerCostCents === "function" ? opts.providerCostCents(response) : opts.providerCostCents;
105
+ }
106
+ function withCost(event, opts, response) {
107
+ const cost = resolveCost(opts, response);
108
+ if (cost !== void 0) event.providerCostCents = cost;
109
+ return event;
110
+ }
97
111
  function fireAndForget(p, onError) {
98
112
  p.catch((err) => (onError ?? ((e) => console.warn("[tollgate] track failed:", e)))(err));
99
113
  }
114
+ function isAsyncIterable(x) {
115
+ return x != null && typeof x[Symbol.asyncIterator] === "function";
116
+ }
117
+ function instrumentStream(stream, onChunk, onDone) {
118
+ let finished = false;
119
+ const finish = () => {
120
+ if (finished) return;
121
+ finished = true;
122
+ onDone();
123
+ };
124
+ return new Proxy(stream, {
125
+ get(target, prop, recv) {
126
+ if (prop === Symbol.asyncIterator) {
127
+ return function instrumentedIterator() {
128
+ const inner = target[Symbol.asyncIterator]();
129
+ return {
130
+ async next(...a) {
131
+ const r = await inner.next(...a);
132
+ if (r.done) finish();
133
+ else onChunk(r.value);
134
+ return r;
135
+ },
136
+ async return(v) {
137
+ finish();
138
+ return inner.return ? inner.return(v) : { done: true, value: v };
139
+ },
140
+ async throw(e) {
141
+ finish();
142
+ if (inner.throw) return inner.throw(e);
143
+ throw e;
144
+ },
145
+ [Symbol.asyncIterator]() {
146
+ return this;
147
+ }
148
+ };
149
+ };
150
+ }
151
+ return Reflect.get(target, prop, recv);
152
+ }
153
+ });
154
+ }
100
155
  function anthropicEventFrom(msg, opts) {
101
156
  const usage = msg?.usage;
102
157
  if (!usage) return null;
@@ -104,11 +159,11 @@ function anthropicEventFrom(msg, opts) {
104
159
  const fivem = usage.cache_creation?.ephemeral_5m_input_tokens;
105
160
  const oneh = usage.cache_creation?.ephemeral_1h_input_tokens;
106
161
  const hasSplit = fivem !== void 0 || oneh !== void 0;
107
- return {
162
+ const event = {
108
163
  customerId: opts.customerId,
109
164
  agentId: opts.agentId,
110
165
  runId,
111
- provider: "anthropic",
166
+ provider: opts.provider ?? "anthropic",
112
167
  model: msg.model ?? "unknown",
113
168
  tokensIn: usage.input_tokens ?? 0,
114
169
  tokensOut: usage.output_tokens ?? 0,
@@ -118,12 +173,32 @@ function anthropicEventFrom(msg, opts) {
118
173
  revenueUnitCents: resolveRevenue(opts, msg),
119
174
  idempotencyKey: msg.id ?? `${runId}#${randomId()}`
120
175
  };
176
+ return withCost(event, opts, msg);
121
177
  }
122
178
  function wrapAnthropic(client, tollgate, opts) {
123
179
  const messages = client.messages;
124
180
  const original = messages.create.bind(messages);
125
181
  const create = async (...args) => {
126
182
  const result = await original(...args);
183
+ if (isAsyncIterable(result)) {
184
+ const msg = {};
185
+ return instrumentStream(
186
+ result,
187
+ (ev) => {
188
+ if (ev.type === "message_start" && ev.message) {
189
+ msg.id = ev.message.id;
190
+ msg.model = ev.message.model;
191
+ msg.usage = { ...ev.message.usage };
192
+ } else if (ev.type === "message_delta" && ev.usage) {
193
+ msg.usage = { ...msg.usage ?? {}, output_tokens: ev.usage.output_tokens };
194
+ }
195
+ },
196
+ () => {
197
+ const event2 = anthropicEventFrom(msg, opts);
198
+ if (event2) fireAndForget(tollgate.track(event2), opts.onError);
199
+ }
200
+ );
201
+ }
127
202
  const event = anthropicEventFrom(result, opts);
128
203
  if (event) fireAndForget(tollgate.track(event), opts.onError);
129
204
  return result;
@@ -143,11 +218,11 @@ function openAIEventFrom(completion, opts) {
143
218
  const usage = completion?.usage;
144
219
  if (!usage) return null;
145
220
  const runId = resolveRunId(opts, completion.id);
146
- return {
221
+ const event = {
147
222
  customerId: opts.customerId,
148
223
  agentId: opts.agentId,
149
224
  runId,
150
- provider: "openai",
225
+ provider: opts.provider ?? "openai",
151
226
  model: completion.model ?? "unknown",
152
227
  tokensIn: usage.prompt_tokens ?? 0,
153
228
  tokensOut: usage.completion_tokens ?? 0,
@@ -156,12 +231,31 @@ function openAIEventFrom(completion, opts) {
156
231
  revenueUnitCents: resolveRevenue(opts, completion),
157
232
  idempotencyKey: completion.id ?? `${runId}#${randomId()}`
158
233
  };
234
+ return withCost(event, opts, completion);
159
235
  }
160
236
  function wrapOpenAI(client, tollgate, opts) {
161
237
  const completions = client.chat.completions;
162
238
  const original = completions.create.bind(completions);
163
239
  const create = async (...args) => {
164
240
  const result = await original(...args);
241
+ if (isAsyncIterable(result)) {
242
+ let id;
243
+ let model;
244
+ let usage;
245
+ return instrumentStream(
246
+ result,
247
+ (chunk) => {
248
+ if (chunk.id) id = chunk.id;
249
+ if (chunk.model) model = chunk.model;
250
+ if (chunk.usage) usage = chunk.usage;
251
+ },
252
+ () => {
253
+ if (!usage) return;
254
+ const event2 = openAIEventFrom({ id, model, usage }, opts);
255
+ if (event2) fireAndForget(tollgate.track(event2), opts.onError);
256
+ }
257
+ );
258
+ }
165
259
  const event = openAIEventFrom(result, opts);
166
260
  if (event) fireAndForget(tollgate.track(event), opts.onError);
167
261
  return result;
@@ -180,7 +274,57 @@ function wrapOpenAI(client, tollgate, opts) {
180
274
  }
181
275
  });
182
276
  }
277
+ function bedrockEventFrom(usage, model, opts, response = void 0) {
278
+ if (!usage) return null;
279
+ const runId = resolveRunId(opts, void 0);
280
+ const event = {
281
+ customerId: opts.customerId,
282
+ agentId: opts.agentId,
283
+ runId,
284
+ provider: opts.provider ?? "bedrock",
285
+ model,
286
+ tokensIn: usage.inputTokens ?? 0,
287
+ tokensOut: usage.outputTokens ?? 0,
288
+ cachedTokens: usage.cacheReadInputTokens ?? 0,
289
+ cacheWrite5mTokens: usage.cacheWriteInputTokens ?? 0,
290
+ revenueUnitCents: resolveRevenue(opts, response),
291
+ idempotencyKey: `${runId}#${randomId()}`
292
+ };
293
+ return withCost(event, opts, response);
294
+ }
295
+ function wrapBedrock(client, tollgate, opts) {
296
+ const originalSend = client.send.bind(client);
297
+ const send = async (command, ...rest) => {
298
+ const result = await originalSend(command, ...rest);
299
+ const model = command?.input?.modelId ?? "unknown";
300
+ if (result?.stream && isAsyncIterable(result.stream)) {
301
+ let usage;
302
+ result.stream = instrumentStream(
303
+ result.stream,
304
+ (ev) => {
305
+ if (ev.metadata?.usage) usage = ev.metadata.usage;
306
+ },
307
+ () => {
308
+ const event = bedrockEventFrom(usage, model, opts, result);
309
+ if (event) fireAndForget(tollgate.track(event), opts.onError);
310
+ }
311
+ );
312
+ return result;
313
+ }
314
+ if (result?.usage) {
315
+ const event = bedrockEventFrom(result.usage, model, opts, result);
316
+ if (event) fireAndForget(tollgate.track(event), opts.onError);
317
+ }
318
+ return result;
319
+ };
320
+ return new Proxy(client, {
321
+ get(target, prop, recv) {
322
+ if (prop === "send") return send;
323
+ return Reflect.get(target, prop, recv);
324
+ }
325
+ });
326
+ }
183
327
 
184
- export { TollgateError, anthropicEventFrom, createTollgateClient, openAIEventFrom, wrapAnthropic, wrapOpenAI };
328
+ export { TollgateError, anthropicEventFrom, bedrockEventFrom, createTollgateClient, openAIEventFrom, wrapAnthropic, wrapBedrock, wrapOpenAI };
185
329
  //# sourceMappingURL=index.js.map
186
330
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/instrument.ts"],"names":[],"mappings":";AAeO,IAAM,aAAA,GAAN,cAA4B,KAAA,CAAM;AAAA,EACvC,WAAA,CACE,OAAA,EACS,MAAA,EACA,IAAA,EACT;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AAHJ,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AAGT,IAAA,IAAA,CAAK,IAAA,GAAO,eAAA;AAAA,EACd;AACF;AAEA,IAAM,gBAAA,GAAmB,+BAAA;AA6BzB,SAAS,OAAO,GAAA,EAAiC;AAE/C,EAAA,OAAO,OAAO,OAAA,KAAY,WAAA,GAAc,OAAA,CAAQ,GAAA,GAAM,GAAG,CAAA,GAAI,MAAA;AAC/D;AAEA,IAAM,KAAA,GAAQ,CAAC,EAAA,KAAe,IAAI,OAAA,CAAQ,CAAC,CAAA,KAAM,UAAA,CAAW,CAAA,EAAG,EAAE,CAAC,CAAA;AAE3D,SAAS,oBAAA,CAAqB,IAAA,GAA8B,EAAC,EAAmB;AACrF,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,IAAU,MAAA,CAAO,kBAAkB,CAAA;AACvD,EAAA,MAAM,OAAA,GAAA,CAAW,KAAK,OAAA,IAAW,MAAA,CAAO,mBAAmB,CAAA,IAAK,gBAAA,EAAkB,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AACnG,EAAA,MAAM,SAAA,GAAY,KAAK,SAAA,IAAa,GAAA;AACpC,EAAA,MAAM,UAAA,GAAa,KAAK,UAAA,IAAc,CAAA;AACtC,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,IAAS,UAAA,CAAW,KAAA;AAEzC,EAAA,IAAI,OAAO,YAAY,UAAA,EAAY;AACjC,IAAA,MAAM,IAAI,cAAc,mEAA8D,CAAA;AAAA,EACxF;AAEA,EAAA,eAAe,MAAM,KAAA,EAA8C;AACjE,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,MAAM,IAAI,cAAc,6DAAwD,CAAA;AAAA,IAClF;AAEA,IAAA,IAAI,OAAA;AACJ,IAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,UAAA,EAAY,OAAA,EAAA,EAAW;AACtD,MAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,MAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,SAAS,CAAA;AAC5D,MAAA,IAAI;AACF,QAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,CAAA,EAAG,OAAO,CAAA,UAAA,CAAA,EAAc;AAAA,UAChD,MAAA,EAAQ,MAAA;AAAA,UACR,OAAA,EAAS;AAAA,YACP,cAAA,EAAgB,kBAAA;AAAA,YAChB,aAAA,EAAe,UAAU,MAAM,CAAA;AAAA,WACjC;AAAA,UACA,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA;AAAA,UAC1B,QAAQ,UAAA,CAAW;AAAA,SACpB,CAAA;AAGD,QAAA,IAAI,IAAI,EAAA,EAAI;AACV,UAAA,OAAQ,MAAM,IAAI,IAAA,EAAK;AAAA,QACzB;AAGA,QAAA,IAAI,GAAA,CAAI,MAAA,IAAU,GAAA,IAAO,GAAA,CAAI,WAAW,GAAA,EAAK;AAC3C,UAAA,OAAA,GAAU,IAAI,aAAA,CAAc,CAAA,uBAAA,EAA0B,IAAI,MAAM,CAAA,CAAA,CAAA,EAAK,IAAI,MAAM,CAAA;AAAA,QACjF,CAAA,MAAO;AACL,UAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,GAAO,KAAA,CAAM,OAAO,EAAC,CAAE,CAAA;AAC9C,UAAA,MAAM,IAAI,cAAc,CAAA,uBAAA,EAA0B,GAAA,CAAI,MAAM,CAAA,CAAA,CAAA,EAAK,GAAA,CAAI,QAAQ,IAAI,CAAA;AAAA,QACnF;AAAA,MACF,SAAS,GAAA,EAAK;AAEZ,QAAA,IAAI,GAAA,YAAe,iBAAiB,GAAA,CAAI,MAAA,IAAU,IAAI,MAAA,GAAS,GAAA,IAAO,GAAA,CAAI,MAAA,KAAW,GAAA,EAAK;AACxF,UAAA,MAAM,GAAA;AAAA,QACR;AACA,QAAA,OAAA,GAAU,GAAA;AAAA,MACZ,CAAA,SAAE;AACA,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB;AAEA,MAAA,IAAI,UAAU,UAAA,EAAY;AACxB,QAAA,MAAM,KAAA,CAAM,CAAA,IAAK,OAAA,GAAU,GAAG,CAAA;AAAA,MAChC;AAAA,IACF;AAEA,IAAA,MAAM,OAAA,YAAmB,KAAA,GACrB,OAAA,GACA,IAAI,cAAc,qCAAqC,CAAA;AAAA,EAC7D;AAEA,EAAA,SAAS,QAAQ,KAAA,EAA2C;AAC1D,IAAA,OAAO,KAAA,CAAM;AAAA,MACX,YAAY,KAAA,CAAM,UAAA;AAAA,MAClB,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,SAAS,KAAA,CAAM,OAAA;AAAA;AAAA,MAEf,QAAA,EAAU,MAAM,QAAA,IAAY,WAAA;AAAA,MAC5B,KAAA,EAAO,MAAM,KAAA,IAAS,YAAA;AAAA,MACtB,QAAA,EAAU,CAAA;AAAA,MACV,SAAA,EAAW,CAAA;AAAA,MACX,SAAS,KAAA,CAAM,OAAA;AAAA,MACf,kBAAkB,KAAA,CAAM,gBAAA;AAAA,MACxB,cAAA,EAAgB,KAAA,CAAM,cAAA,IAAkB,CAAA,EAAG,MAAM,KAAK,CAAA,QAAA,CAAA;AAAA,MACtD,IAAI,KAAA,CAAM;AAAA,KACX,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,EAAE,OAAO,OAAA,EAAQ;AAC1B;;;AC3HA,SAAS,QAAA,GAAmB;AAC1B,EAAA,MAAM,IAAK,UAAA,CAAmC,MAAA;AAC9C,EAAA,IAAI,CAAA,EAAG,UAAA,EAAY,OAAO,CAAA,CAAE,UAAA,EAAW;AACvC,EAAA,OAAO,CAAA,EAAG,IAAA,CAAK,GAAA,EAAK,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,EAAO,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AAC7D;AAEA,SAAS,YAAA,CAAa,MAAyB,UAAA,EAA6B;AAC1E,EAAA,IAAI,OAAO,IAAA,CAAK,KAAA,KAAU,UAAA,EAAY,OAAO,KAAK,KAAA,EAAM;AACxD,EAAA,OAAO,IAAA,CAAK,KAAA,IAAS,UAAA,IAAc,QAAA,EAAS;AAC9C;AAEA,SAAS,cAAA,CAAe,MAAyB,QAAA,EAAuC;AACtF,EAAA,OAAO,OAAO,KAAK,gBAAA,KAAqB,UAAA,GACpC,KAAK,gBAAA,CAAiB,QAAQ,IAC9B,IAAA,CAAK,gBAAA;AACX;AAEA,SAAS,aAAA,CAAc,GAAqB,OAAA,EAA8C;AACxF,EAAA,CAAA,CAAE,KAAA,CAAM,CAAC,GAAA,KAAA,CAAS,OAAA,KAAY,CAAC,CAAA,KAAM,OAAA,CAAQ,IAAA,CAAK,0BAAA,EAA4B,CAAC,CAAA,CAAA,EAAI,GAAG,CAAC,CAAA;AACzF;AAuBO,SAAS,kBAAA,CACd,KACA,IAAA,EACwB;AACxB,EAAA,MAAM,QAAQ,GAAA,EAAK,KAAA;AACnB,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,KAAA,GAAQ,YAAA,CAAa,IAAA,EAAM,GAAA,CAAI,EAAE,CAAA;AAGvC,EAAA,MAAM,KAAA,GAAQ,MAAM,cAAA,EAAgB,yBAAA;AACpC,EAAA,MAAM,IAAA,GAAO,MAAM,cAAA,EAAgB,yBAAA;AACnC,EAAA,MAAM,QAAA,GAAW,KAAA,KAAU,MAAA,IAAa,IAAA,KAAS,MAAA;AACjD,EAAA,OAAO;AAAA,IACL,YAAY,IAAA,CAAK,UAAA;AAAA,IACjB,SAAS,IAAA,CAAK,OAAA;AAAA,IACd,KAAA;AAAA,IACA,QAAA,EAAU,WAAA;AAAA,IACV,KAAA,EAAO,IAAI,KAAA,IAAS,SAAA;AAAA,IACpB,QAAA,EAAU,MAAM,YAAA,IAAgB,CAAA;AAAA,IAChC,SAAA,EAAW,MAAM,aAAA,IAAiB,CAAA;AAAA,IAClC,YAAA,EAAc,MAAM,uBAAA,IAA2B,CAAA;AAAA,IAC/C,kBAAA,EAAoB,QAAA,GAAW,KAAA,IAAS,CAAA,GAAI,MAAM,2BAAA,IAA+B,CAAA;AAAA,IACjF,kBAAA,EAAoB,QAAA,GAAW,IAAA,IAAQ,CAAA,GAAI,CAAA;AAAA,IAC3C,gBAAA,EAAkB,cAAA,CAAe,IAAA,EAAM,GAAG,CAAA;AAAA,IAC1C,gBAAgB,GAAA,CAAI,EAAA,IAAM,GAAG,KAAK,CAAA,CAAA,EAAI,UAAU,CAAA;AAAA,GAClD;AACF;AAOO,SAAS,aAAA,CACd,MAAA,EACA,QAAA,EACA,IAAA,EACG;AACH,EAAA,MAAM,WAAW,MAAA,CAAO,QAAA;AACxB,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAA;AAE9C,EAAA,MAAM,MAAA,GAAS,UAAU,IAAA,KAAoC;AAC3D,IAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,GAAG,IAAI,CAAA;AACrC,IAAA,MAAM,KAAA,GAAQ,kBAAA,CAAmB,MAAA,EAA4B,IAAI,CAAA;AACjE,IAAA,IAAI,OAAO,aAAA,CAAc,QAAA,CAAS,MAAM,KAAK,CAAA,EAAG,KAAK,OAAO,CAAA;AAC5D,IAAA,OAAO,MAAA;AAAA,EACT,CAAA;AAEA,EAAA,OAAO,IAAI,MAAM,MAAA,EAAQ;AAAA,IACvB,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAM;AACtB,MAAA,IAAI,SAAS,UAAA,EAAY;AACvB,QAAA,OAAO,IAAI,MAAM,QAAA,EAAU;AAAA,UACzB,GAAA,EAAK,CAAC,CAAA,EAAG,CAAA,EAAG,CAAA,KAAO,CAAA,KAAM,QAAA,GAAW,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,CAAA,EAAG,CAAA,EAAG,CAAC;AAAA,SACjE,CAAA;AAAA,MACH;AACA,MAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,IAAI,CAAA;AAAA,IACvC;AAAA,GACD,CAAA;AACH;AAiBO,SAAS,eAAA,CACd,YACA,IAAA,EACwB;AACxB,EAAA,MAAM,QAAQ,UAAA,EAAY,KAAA;AAC1B,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,KAAA,GAAQ,YAAA,CAAa,IAAA,EAAM,UAAA,CAAW,EAAE,CAAA;AAC9C,EAAA,OAAO;AAAA,IACL,YAAY,IAAA,CAAK,UAAA;AAAA,IACjB,SAAS,IAAA,CAAK,OAAA;AAAA,IACd,KAAA;AAAA,IACA,QAAA,EAAU,QAAA;AAAA,IACV,KAAA,EAAO,WAAW,KAAA,IAAS,SAAA;AAAA,IAC3B,QAAA,EAAU,MAAM,aAAA,IAAiB,CAAA;AAAA,IACjC,SAAA,EAAW,MAAM,iBAAA,IAAqB,CAAA;AAAA,IACtC,eAAA,EAAiB,KAAA,CAAM,yBAAA,EAA2B,gBAAA,IAAoB,CAAA;AAAA,IACtE,YAAA,EAAc,KAAA,CAAM,qBAAA,EAAuB,aAAA,IAAiB,CAAA;AAAA,IAC5D,gBAAA,EAAkB,cAAA,CAAe,IAAA,EAAM,UAAU,CAAA;AAAA,IACjD,gBAAgB,UAAA,CAAW,EAAA,IAAM,GAAG,KAAK,CAAA,CAAA,EAAI,UAAU,CAAA;AAAA,GACzD;AACF;AAOO,SAAS,UAAA,CACd,MAAA,EACA,QAAA,EACA,IAAA,EACG;AACH,EAAA,MAAM,WAAA,GAAc,OAAO,IAAA,CAAK,WAAA;AAChC,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,MAAA,CAAO,IAAA,CAAK,WAAW,CAAA;AAEpD,EAAA,MAAM,MAAA,GAAS,UAAU,IAAA,KAAoC;AAC3D,IAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,GAAG,IAAI,CAAA;AACrC,IAAA,MAAM,KAAA,GAAQ,eAAA,CAAgB,MAAA,EAA4B,IAAI,CAAA;AAC9D,IAAA,IAAI,OAAO,aAAA,CAAc,QAAA,CAAS,MAAM,KAAK,CAAA,EAAG,KAAK,OAAO,CAAA;AAC5D,IAAA,OAAO,MAAA;AAAA,EACT,CAAA;AAEA,EAAA,OAAO,IAAI,MAAM,MAAA,EAAQ;AAAA,IACvB,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAM;AACtB,MAAA,IAAI,SAAS,MAAA,EAAQ;AACnB,QAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,MAAM,IAAI,CAAA;AAC3C,QAAA,OAAO,IAAI,MAAM,IAAA,EAAM;AAAA,UACrB,GAAA,EAAK,CAAC,CAAA,EAAG,CAAA,EAAG,MACV,CAAA,KAAM,aAAA,GACF,IAAI,KAAA,CAAM,WAAA,EAAa;AAAA,YACrB,GAAA,EAAK,CAAC,EAAA,EAAI,EAAA,EAAI,EAAA,KAAQ,EAAA,KAAO,QAAA,GAAW,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,EAAA,EAAI,EAAA,EAAI,EAAE;AAAA,WACxE,CAAA,GACD,OAAA,CAAQ,GAAA,CAAI,CAAA,EAAG,GAAG,CAAC;AAAA,SAC1B,CAAA;AAAA,MACH;AACA,MAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,IAAI,CAAA;AAAA,IACvC;AAAA,GACD,CAAA;AACH","file":"index.js","sourcesContent":["import type { Provider, RunOutcome, TrackEventInput, TrackResult } 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}\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 async function track(event: TrackEventInput): Promise<TrackResult> {\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}/api/track`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(event),\n signal: controller.signal,\n });\n\n // 200 = idempotent duplicate, 201 = created. Both are success.\n if (res.ok) {\n return (await res.json()) as TrackResult;\n }\n\n // Retry transient failures; fail fast on 4xx (except 429).\n if (res.status >= 500 || res.status === 429) {\n lastErr = new TollgateError(`Tollgate track failed (${res.status})`, res.status);\n } else {\n const body = await res.json().catch(() => ({}));\n throw new TollgateError(`Tollgate track failed (${res.status})`, res.status, body);\n }\n } catch (err) {\n // Don't retry deterministic client errors.\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 track failed after retries');\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 };\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\nimport type { TollgateClient } from './client';\nimport type { 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 /** Revenue per call in cents (or a function of the response). */\n revenueUnitCents?: 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 fireAndForget(p: Promise<unknown>, onError?: InstrumentOptions['onError']): void {\n p.catch((err) => (onError ?? ((e) => console.warn('[tollgate] track failed:', e)))(err));\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\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 return {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n 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}\n\ninterface AnthropicLike {\n messages: { create: (...args: never[]) => Promise<unknown> };\n}\n\n/** Wrap an Anthropic client so `messages.create` auto-reports usage. */\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 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 ---------------------------------------------------------------\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 return {\n customerId: opts.customerId,\n agentId: opts.agentId,\n runId,\n 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}\n\ninterface OpenAILike {\n chat: { completions: { create: (...args: never[]) => Promise<unknown> } };\n}\n\n/** Wrap an OpenAI client so `chat.completions.create` auto-reports usage. */\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 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"]}
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tollgateai/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "description": "Track real LLM model usage and compute live gross margin with Tollgate.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",