@tollgateai/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # @tollgateai/sdk
2
+
3
+ Track **real** LLM model usage and compute live gross margin with
4
+ [Tollgate](https://tollgateapp.vercel.app). The SDK reads the actual `usage`
5
+ object off each provider response — you never hand-count tokens.
6
+
7
+ ```bash
8
+ npm install @tollgateai/sdk
9
+ # or: pnpm add @tollgateai/sdk / yarn add @tollgateai/sdk
10
+ ```
11
+
12
+ Create an API key in **Tollgate → Integrations**, then set:
13
+
14
+ ```bash
15
+ TOLLGATE_API_KEY=tg_live_xxx
16
+ # optional, defaults to the hosted app:
17
+ TOLLGATE_BASE_URL=https://tollgateapp.vercel.app
18
+ ```
19
+
20
+ ## Auto-instrumentation (recommended)
21
+
22
+ Wrap your provider client once; every call reports real usage in the background.
23
+
24
+ ### Anthropic
25
+
26
+ ```ts
27
+ import Anthropic from '@anthropic-ai/sdk';
28
+ import { createTollgateClient, wrapAnthropic } from '@tollgateai/sdk';
29
+
30
+ const tollgate = createTollgateClient(); // reads TOLLGATE_API_KEY
31
+ const anthropic = wrapAnthropic(new Anthropic(), tollgate, {
32
+ customerId: 'cust_A', // your end customer
33
+ revenueUnitCents: 50, // what you charge for this unit ($0.50)
34
+ });
35
+
36
+ // Use the client normally — usage is tracked automatically.
37
+ await anthropic.messages.create({
38
+ model: 'claude-sonnet-4-6',
39
+ max_tokens: 512,
40
+ messages: [{ role: 'user', content: 'Summarize this ticket…' }],
41
+ });
42
+ ```
43
+
44
+ ### OpenAI
45
+
46
+ ```ts
47
+ import OpenAI from 'openai';
48
+ import { createTollgateClient, wrapOpenAI } from '@tollgateai/sdk';
49
+
50
+ const tollgate = createTollgateClient();
51
+ const openai = wrapOpenAI(new OpenAI(), tollgate, { customerId: 'cust_A' });
52
+
53
+ await openai.chat.completions.create({
54
+ model: 'gpt-4o',
55
+ messages: [{ role: 'user', content: 'Hello' }],
56
+ });
57
+ ```
58
+
59
+ `revenueUnitCents` may also be a function of the response, e.g.
60
+ `revenueUnitCents: (res) => res.someField ? 50 : 0`.
61
+
62
+ ## Manual tracking
63
+
64
+ For providers without a wrapper (Bedrock, custom gateways) or full control:
65
+
66
+ ```ts
67
+ import { createTollgateClient } from '@tollgateai/sdk';
68
+
69
+ const tollgate = createTollgateClient({ apiKey: process.env.TOLLGATE_API_KEY });
70
+
71
+ await tollgate.track({
72
+ customerId: 'cust_A',
73
+ runId: 'run_12345',
74
+ provider: 'anthropic',
75
+ model: 'claude-sonnet-4-6',
76
+ tokensIn: 1200,
77
+ tokensOut: 450,
78
+ reasoningTokens: 0,
79
+ cachedTokens: 0,
80
+ revenueUnitCents: 50,
81
+ idempotencyKey: 'run_12345#step_1', // exactly-once: safe to retry
82
+ });
83
+ ```
84
+
85
+ ## Notes
86
+
87
+ - **Idempotent.** Events are deduplicated on `idempotencyKey` (auto-set to the
88
+ provider response id by the wrappers), so retries never double-count.
89
+ - **No prompt content is ever sent** — only token counts and metadata.
90
+ - **Streaming** responses are not auto-tracked yet (the wrappers only report when
91
+ a non-streaming `usage` object is present). Track those manually for now.
92
+ - **Non-blocking.** Auto-instrumented tracking runs in the background; failures
93
+ are passed to `onError` (default `console.warn`) and never break your call.
94
+
95
+ ## API
96
+
97
+ - `createTollgateClient(options?)` → `{ track(event) }`
98
+ - `wrapAnthropic(client, tollgate, options)` → instrumented Anthropic client
99
+ - `wrapOpenAI(client, tollgate, options)` → instrumented OpenAI client
100
+ - `anthropicEventFrom(msg, options)` / `openAIEventFrom(completion, options)` →
101
+ build a track payload manually from a provider response
102
+
103
+ Licensed for use with Tollgate. Not open source.
package/dist/index.cjs ADDED
@@ -0,0 +1,172 @@
1
+ 'use strict';
2
+
3
+ // src/client.ts
4
+ var TollgateError = class extends Error {
5
+ constructor(message, status, body) {
6
+ super(message);
7
+ this.status = status;
8
+ this.body = body;
9
+ this.name = "TollgateError";
10
+ }
11
+ };
12
+ var DEFAULT_BASE_URL = "https://tollgateapp.vercel.app";
13
+ function getEnv(key) {
14
+ return typeof process !== "undefined" ? process.env?.[key] : void 0;
15
+ }
16
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
17
+ function createTollgateClient(opts = {}) {
18
+ const apiKey = opts.apiKey ?? getEnv("TOLLGATE_API_KEY");
19
+ const baseUrl = (opts.baseUrl ?? getEnv("TOLLGATE_BASE_URL") ?? DEFAULT_BASE_URL).replace(/\/$/, "");
20
+ const timeoutMs = opts.timeoutMs ?? 1e4;
21
+ const maxRetries = opts.maxRetries ?? 2;
22
+ const doFetch = opts.fetch ?? globalThis.fetch;
23
+ if (typeof doFetch !== "function") {
24
+ throw new TollgateError("No fetch implementation available \u2014 pass `fetch` in options.");
25
+ }
26
+ async function track(event) {
27
+ if (!apiKey) {
28
+ throw new TollgateError("Missing API key \u2014 set opts.apiKey or TOLLGATE_API_KEY.");
29
+ }
30
+ let lastErr;
31
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
32
+ const controller = new AbortController();
33
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
34
+ try {
35
+ const res = await doFetch(`${baseUrl}/api/track`, {
36
+ method: "POST",
37
+ headers: {
38
+ "Content-Type": "application/json",
39
+ Authorization: `Bearer ${apiKey}`
40
+ },
41
+ body: JSON.stringify(event),
42
+ signal: controller.signal
43
+ });
44
+ if (res.ok) {
45
+ return await res.json();
46
+ }
47
+ if (res.status >= 500 || res.status === 429) {
48
+ lastErr = new TollgateError(`Tollgate track failed (${res.status})`, res.status);
49
+ } else {
50
+ const body = await res.json().catch(() => ({}));
51
+ throw new TollgateError(`Tollgate track failed (${res.status})`, res.status, body);
52
+ }
53
+ } catch (err) {
54
+ if (err instanceof TollgateError && err.status && err.status < 500 && err.status !== 429) {
55
+ throw err;
56
+ }
57
+ lastErr = err;
58
+ } finally {
59
+ clearTimeout(timer);
60
+ }
61
+ if (attempt < maxRetries) {
62
+ await sleep(2 ** attempt * 200);
63
+ }
64
+ }
65
+ throw lastErr instanceof Error ? lastErr : new TollgateError("Tollgate track failed after retries");
66
+ }
67
+ return { track };
68
+ }
69
+
70
+ // src/instrument.ts
71
+ function randomId() {
72
+ const c = globalThis.crypto;
73
+ if (c?.randomUUID) return c.randomUUID();
74
+ return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
75
+ }
76
+ function resolveRunId(opts, responseId) {
77
+ if (typeof opts.runId === "function") return opts.runId();
78
+ return opts.runId ?? responseId ?? randomId();
79
+ }
80
+ function resolveRevenue(opts, response) {
81
+ return typeof opts.revenueUnitCents === "function" ? opts.revenueUnitCents(response) : opts.revenueUnitCents;
82
+ }
83
+ function fireAndForget(p, onError) {
84
+ p.catch((err) => (onError ?? ((e) => console.warn("[tollgate] track failed:", e)))(err));
85
+ }
86
+ function anthropicEventFrom(msg, opts) {
87
+ const usage = msg?.usage;
88
+ if (!usage) return null;
89
+ const runId = resolveRunId(opts, msg.id);
90
+ return {
91
+ customerId: opts.customerId,
92
+ agentId: opts.agentId,
93
+ runId,
94
+ provider: "anthropic",
95
+ model: msg.model ?? "unknown",
96
+ tokensIn: usage.input_tokens ?? 0,
97
+ tokensOut: usage.output_tokens ?? 0,
98
+ cachedTokens: usage.cache_read_input_tokens ?? 0,
99
+ revenueUnitCents: resolveRevenue(opts, msg),
100
+ idempotencyKey: msg.id ?? `${runId}#${randomId()}`
101
+ };
102
+ }
103
+ function wrapAnthropic(client, tollgate, opts) {
104
+ const messages = client.messages;
105
+ const original = messages.create.bind(messages);
106
+ const create = async (...args) => {
107
+ const result = await original(...args);
108
+ const event = anthropicEventFrom(result, opts);
109
+ if (event) fireAndForget(tollgate.track(event), opts.onError);
110
+ return result;
111
+ };
112
+ return new Proxy(client, {
113
+ get(target, prop, recv) {
114
+ if (prop === "messages") {
115
+ return new Proxy(messages, {
116
+ get: (m, p, r) => p === "create" ? create : Reflect.get(m, p, r)
117
+ });
118
+ }
119
+ return Reflect.get(target, prop, recv);
120
+ }
121
+ });
122
+ }
123
+ function openAIEventFrom(completion, opts) {
124
+ const usage = completion?.usage;
125
+ if (!usage) return null;
126
+ const runId = resolveRunId(opts, completion.id);
127
+ return {
128
+ customerId: opts.customerId,
129
+ agentId: opts.agentId,
130
+ runId,
131
+ provider: "openai",
132
+ model: completion.model ?? "unknown",
133
+ tokensIn: usage.prompt_tokens ?? 0,
134
+ tokensOut: usage.completion_tokens ?? 0,
135
+ reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? 0,
136
+ cachedTokens: usage.prompt_tokens_details?.cached_tokens ?? 0,
137
+ revenueUnitCents: resolveRevenue(opts, completion),
138
+ idempotencyKey: completion.id ?? `${runId}#${randomId()}`
139
+ };
140
+ }
141
+ function wrapOpenAI(client, tollgate, opts) {
142
+ const completions = client.chat.completions;
143
+ const original = completions.create.bind(completions);
144
+ const create = async (...args) => {
145
+ const result = await original(...args);
146
+ const event = openAIEventFrom(result, opts);
147
+ if (event) fireAndForget(tollgate.track(event), opts.onError);
148
+ return result;
149
+ };
150
+ return new Proxy(client, {
151
+ get(target, prop, recv) {
152
+ if (prop === "chat") {
153
+ const chat = Reflect.get(target, prop, recv);
154
+ return new Proxy(chat, {
155
+ get: (c, p, r) => p === "completions" ? new Proxy(completions, {
156
+ get: (co, pp, rr) => pp === "create" ? create : Reflect.get(co, pp, rr)
157
+ }) : Reflect.get(c, p, r)
158
+ });
159
+ }
160
+ return Reflect.get(target, prop, recv);
161
+ }
162
+ });
163
+ }
164
+
165
+ exports.TollgateError = TollgateError;
166
+ exports.anthropicEventFrom = anthropicEventFrom;
167
+ exports.createTollgateClient = createTollgateClient;
168
+ exports.openAIEventFrom = openAIEventFrom;
169
+ exports.wrapAnthropic = wrapAnthropic;
170
+ exports.wrapOpenAI = wrapOpenAI;
171
+ //# sourceMappingURL=index.cjs.map
172
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/instrument.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { 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://tollgateapp.vercel.app';\n\nexport interface TollgateClient {\n /** Report a single usage event. Idempotent on `idempotencyKey`. */\n track(event: TrackEventInput): 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 return { track };\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}\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 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 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"]}
@@ -0,0 +1,116 @@
1
+ type Provider = 'anthropic' | 'openai' | 'bedrock';
2
+ type EventType = 'llm' | 'tool' | 'retrieval';
3
+ /** The payload accepted by `POST /api/track`. Money is in integer cents. */
4
+ interface TrackEventInput {
5
+ /** Your end customer's stable id (e.g. "cust_A"). Used for margin attribution. */
6
+ customerId: string;
7
+ /** Logical run/request id (e.g. "run_12345"). */
8
+ runId: string;
9
+ /** Optional agent/workflow id for per-agent margin. */
10
+ agentId?: string;
11
+ /** Defaults to "llm". */
12
+ type?: EventType;
13
+ provider: Provider;
14
+ model: string;
15
+ tokensIn?: number;
16
+ tokensOut?: number;
17
+ /** Reasoning/thinking tokens — billed at output rates. */
18
+ reasoningTokens?: number;
19
+ /** Cached / cache-read input tokens. */
20
+ cachedTokens?: number;
21
+ toolCalls?: number;
22
+ toolName?: string;
23
+ /** Per-event revenue contribution in cents (e.g. 50 for a $0.50 unit). */
24
+ revenueUnitCents?: number;
25
+ /** Required for exactly-once ingestion (e.g. "run_12345#step_1"). */
26
+ idempotencyKey: string;
27
+ /** ISO timestamp; defaults to server receive time. */
28
+ ts?: string;
29
+ }
30
+ interface TrackResult {
31
+ status: 'created' | 'duplicate' | string;
32
+ eventId: string;
33
+ }
34
+
35
+ interface TollgateClientOptions {
36
+ /** Account API key (`tg_live_…`). Falls back to `process.env.TOLLGATE_API_KEY`. */
37
+ apiKey?: string;
38
+ /** Base URL of your Tollgate deployment. Defaults to `TOLLGATE_BASE_URL` or production. */
39
+ baseUrl?: string;
40
+ /** Per-request timeout in ms. Default 10_000. */
41
+ timeoutMs?: number;
42
+ /** Retry attempts on network error / 5xx / 429. Default 2. */
43
+ maxRetries?: number;
44
+ /** Custom fetch (for testing or non-standard runtimes). Defaults to global fetch. */
45
+ fetch?: typeof fetch;
46
+ }
47
+ declare class TollgateError extends Error {
48
+ readonly status?: number | undefined;
49
+ readonly body?: unknown | undefined;
50
+ constructor(message: string, status?: number | undefined, body?: unknown | undefined);
51
+ }
52
+ interface TollgateClient {
53
+ /** Report a single usage event. Idempotent on `idempotencyKey`. */
54
+ track(event: TrackEventInput): Promise<TrackResult>;
55
+ }
56
+ declare function createTollgateClient(opts?: TollgateClientOptions): TollgateClient;
57
+
58
+ interface InstrumentOptions {
59
+ /** Your end customer's stable id. Required for margin attribution. */
60
+ customerId: string;
61
+ /** Optional agent/workflow id. */
62
+ agentId?: string;
63
+ /** Revenue per call in cents (or a function of the response). */
64
+ revenueUnitCents?: number | ((response: unknown) => number | undefined);
65
+ /** Override the run id; defaults to the provider response id. */
66
+ runId?: string | (() => string);
67
+ /** Called if a background track() fails. Defaults to console.warn. */
68
+ onError?: (err: unknown) => void;
69
+ }
70
+ interface AnthropicUsage {
71
+ input_tokens?: number;
72
+ output_tokens?: number;
73
+ cache_read_input_tokens?: number;
74
+ }
75
+ interface AnthropicMessage {
76
+ id?: string;
77
+ model?: string;
78
+ usage?: AnthropicUsage;
79
+ }
80
+ /** Map a non-streaming Anthropic message to a track payload (or null if no usage). */
81
+ declare function anthropicEventFrom(msg: AnthropicMessage, opts: InstrumentOptions): TrackEventInput | null;
82
+ interface AnthropicLike {
83
+ messages: {
84
+ create: (...args: never[]) => Promise<unknown>;
85
+ };
86
+ }
87
+ /** Wrap an Anthropic client so `messages.create` auto-reports usage. */
88
+ declare function wrapAnthropic<T extends AnthropicLike>(client: T, tollgate: TollgateClient, opts: InstrumentOptions): T;
89
+ interface OpenAIUsage {
90
+ prompt_tokens?: number;
91
+ completion_tokens?: number;
92
+ completion_tokens_details?: {
93
+ reasoning_tokens?: number;
94
+ };
95
+ prompt_tokens_details?: {
96
+ cached_tokens?: number;
97
+ };
98
+ }
99
+ interface OpenAICompletion {
100
+ id?: string;
101
+ model?: string;
102
+ usage?: OpenAIUsage;
103
+ }
104
+ /** Map a non-streaming OpenAI chat completion to a track payload (or null). */
105
+ declare function openAIEventFrom(completion: OpenAICompletion, opts: InstrumentOptions): TrackEventInput | null;
106
+ interface OpenAILike {
107
+ chat: {
108
+ completions: {
109
+ create: (...args: never[]) => Promise<unknown>;
110
+ };
111
+ };
112
+ }
113
+ /** Wrap an OpenAI client so `chat.completions.create` auto-reports usage. */
114
+ declare function wrapOpenAI<T extends OpenAILike>(client: T, tollgate: TollgateClient, opts: InstrumentOptions): T;
115
+
116
+ export { type EventType, type InstrumentOptions, type Provider, type TollgateClient, type TollgateClientOptions, TollgateError, type TrackEventInput, type TrackResult, anthropicEventFrom, createTollgateClient, openAIEventFrom, wrapAnthropic, wrapOpenAI };
@@ -0,0 +1,116 @@
1
+ type Provider = 'anthropic' | 'openai' | 'bedrock';
2
+ type EventType = 'llm' | 'tool' | 'retrieval';
3
+ /** The payload accepted by `POST /api/track`. Money is in integer cents. */
4
+ interface TrackEventInput {
5
+ /** Your end customer's stable id (e.g. "cust_A"). Used for margin attribution. */
6
+ customerId: string;
7
+ /** Logical run/request id (e.g. "run_12345"). */
8
+ runId: string;
9
+ /** Optional agent/workflow id for per-agent margin. */
10
+ agentId?: string;
11
+ /** Defaults to "llm". */
12
+ type?: EventType;
13
+ provider: Provider;
14
+ model: string;
15
+ tokensIn?: number;
16
+ tokensOut?: number;
17
+ /** Reasoning/thinking tokens — billed at output rates. */
18
+ reasoningTokens?: number;
19
+ /** Cached / cache-read input tokens. */
20
+ cachedTokens?: number;
21
+ toolCalls?: number;
22
+ toolName?: string;
23
+ /** Per-event revenue contribution in cents (e.g. 50 for a $0.50 unit). */
24
+ revenueUnitCents?: number;
25
+ /** Required for exactly-once ingestion (e.g. "run_12345#step_1"). */
26
+ idempotencyKey: string;
27
+ /** ISO timestamp; defaults to server receive time. */
28
+ ts?: string;
29
+ }
30
+ interface TrackResult {
31
+ status: 'created' | 'duplicate' | string;
32
+ eventId: string;
33
+ }
34
+
35
+ interface TollgateClientOptions {
36
+ /** Account API key (`tg_live_…`). Falls back to `process.env.TOLLGATE_API_KEY`. */
37
+ apiKey?: string;
38
+ /** Base URL of your Tollgate deployment. Defaults to `TOLLGATE_BASE_URL` or production. */
39
+ baseUrl?: string;
40
+ /** Per-request timeout in ms. Default 10_000. */
41
+ timeoutMs?: number;
42
+ /** Retry attempts on network error / 5xx / 429. Default 2. */
43
+ maxRetries?: number;
44
+ /** Custom fetch (for testing or non-standard runtimes). Defaults to global fetch. */
45
+ fetch?: typeof fetch;
46
+ }
47
+ declare class TollgateError extends Error {
48
+ readonly status?: number | undefined;
49
+ readonly body?: unknown | undefined;
50
+ constructor(message: string, status?: number | undefined, body?: unknown | undefined);
51
+ }
52
+ interface TollgateClient {
53
+ /** Report a single usage event. Idempotent on `idempotencyKey`. */
54
+ track(event: TrackEventInput): Promise<TrackResult>;
55
+ }
56
+ declare function createTollgateClient(opts?: TollgateClientOptions): TollgateClient;
57
+
58
+ interface InstrumentOptions {
59
+ /** Your end customer's stable id. Required for margin attribution. */
60
+ customerId: string;
61
+ /** Optional agent/workflow id. */
62
+ agentId?: string;
63
+ /** Revenue per call in cents (or a function of the response). */
64
+ revenueUnitCents?: number | ((response: unknown) => number | undefined);
65
+ /** Override the run id; defaults to the provider response id. */
66
+ runId?: string | (() => string);
67
+ /** Called if a background track() fails. Defaults to console.warn. */
68
+ onError?: (err: unknown) => void;
69
+ }
70
+ interface AnthropicUsage {
71
+ input_tokens?: number;
72
+ output_tokens?: number;
73
+ cache_read_input_tokens?: number;
74
+ }
75
+ interface AnthropicMessage {
76
+ id?: string;
77
+ model?: string;
78
+ usage?: AnthropicUsage;
79
+ }
80
+ /** Map a non-streaming Anthropic message to a track payload (or null if no usage). */
81
+ declare function anthropicEventFrom(msg: AnthropicMessage, opts: InstrumentOptions): TrackEventInput | null;
82
+ interface AnthropicLike {
83
+ messages: {
84
+ create: (...args: never[]) => Promise<unknown>;
85
+ };
86
+ }
87
+ /** Wrap an Anthropic client so `messages.create` auto-reports usage. */
88
+ declare function wrapAnthropic<T extends AnthropicLike>(client: T, tollgate: TollgateClient, opts: InstrumentOptions): T;
89
+ interface OpenAIUsage {
90
+ prompt_tokens?: number;
91
+ completion_tokens?: number;
92
+ completion_tokens_details?: {
93
+ reasoning_tokens?: number;
94
+ };
95
+ prompt_tokens_details?: {
96
+ cached_tokens?: number;
97
+ };
98
+ }
99
+ interface OpenAICompletion {
100
+ id?: string;
101
+ model?: string;
102
+ usage?: OpenAIUsage;
103
+ }
104
+ /** Map a non-streaming OpenAI chat completion to a track payload (or null). */
105
+ declare function openAIEventFrom(completion: OpenAICompletion, opts: InstrumentOptions): TrackEventInput | null;
106
+ interface OpenAILike {
107
+ chat: {
108
+ completions: {
109
+ create: (...args: never[]) => Promise<unknown>;
110
+ };
111
+ };
112
+ }
113
+ /** Wrap an OpenAI client so `chat.completions.create` auto-reports usage. */
114
+ declare function wrapOpenAI<T extends OpenAILike>(client: T, tollgate: TollgateClient, opts: InstrumentOptions): T;
115
+
116
+ export { type EventType, type InstrumentOptions, type Provider, type TollgateClient, type TollgateClientOptions, TollgateError, type TrackEventInput, type TrackResult, anthropicEventFrom, createTollgateClient, openAIEventFrom, wrapAnthropic, wrapOpenAI };
package/dist/index.js ADDED
@@ -0,0 +1,165 @@
1
+ // src/client.ts
2
+ var TollgateError = class extends Error {
3
+ constructor(message, status, body) {
4
+ super(message);
5
+ this.status = status;
6
+ this.body = body;
7
+ this.name = "TollgateError";
8
+ }
9
+ };
10
+ var DEFAULT_BASE_URL = "https://tollgateapp.vercel.app";
11
+ function getEnv(key) {
12
+ return typeof process !== "undefined" ? process.env?.[key] : void 0;
13
+ }
14
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
15
+ function createTollgateClient(opts = {}) {
16
+ const apiKey = opts.apiKey ?? getEnv("TOLLGATE_API_KEY");
17
+ const baseUrl = (opts.baseUrl ?? getEnv("TOLLGATE_BASE_URL") ?? DEFAULT_BASE_URL).replace(/\/$/, "");
18
+ const timeoutMs = opts.timeoutMs ?? 1e4;
19
+ const maxRetries = opts.maxRetries ?? 2;
20
+ const doFetch = opts.fetch ?? globalThis.fetch;
21
+ if (typeof doFetch !== "function") {
22
+ throw new TollgateError("No fetch implementation available \u2014 pass `fetch` in options.");
23
+ }
24
+ async function track(event) {
25
+ if (!apiKey) {
26
+ throw new TollgateError("Missing API key \u2014 set opts.apiKey or TOLLGATE_API_KEY.");
27
+ }
28
+ let lastErr;
29
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
30
+ const controller = new AbortController();
31
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
32
+ try {
33
+ const res = await doFetch(`${baseUrl}/api/track`, {
34
+ method: "POST",
35
+ headers: {
36
+ "Content-Type": "application/json",
37
+ Authorization: `Bearer ${apiKey}`
38
+ },
39
+ body: JSON.stringify(event),
40
+ signal: controller.signal
41
+ });
42
+ if (res.ok) {
43
+ return await res.json();
44
+ }
45
+ if (res.status >= 500 || res.status === 429) {
46
+ lastErr = new TollgateError(`Tollgate track failed (${res.status})`, res.status);
47
+ } else {
48
+ const body = await res.json().catch(() => ({}));
49
+ throw new TollgateError(`Tollgate track failed (${res.status})`, res.status, body);
50
+ }
51
+ } catch (err) {
52
+ if (err instanceof TollgateError && err.status && err.status < 500 && err.status !== 429) {
53
+ throw err;
54
+ }
55
+ lastErr = err;
56
+ } finally {
57
+ clearTimeout(timer);
58
+ }
59
+ if (attempt < maxRetries) {
60
+ await sleep(2 ** attempt * 200);
61
+ }
62
+ }
63
+ throw lastErr instanceof Error ? lastErr : new TollgateError("Tollgate track failed after retries");
64
+ }
65
+ return { track };
66
+ }
67
+
68
+ // src/instrument.ts
69
+ function randomId() {
70
+ const c = globalThis.crypto;
71
+ if (c?.randomUUID) return c.randomUUID();
72
+ return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
73
+ }
74
+ function resolveRunId(opts, responseId) {
75
+ if (typeof opts.runId === "function") return opts.runId();
76
+ return opts.runId ?? responseId ?? randomId();
77
+ }
78
+ function resolveRevenue(opts, response) {
79
+ return typeof opts.revenueUnitCents === "function" ? opts.revenueUnitCents(response) : opts.revenueUnitCents;
80
+ }
81
+ function fireAndForget(p, onError) {
82
+ p.catch((err) => (onError ?? ((e) => console.warn("[tollgate] track failed:", e)))(err));
83
+ }
84
+ function anthropicEventFrom(msg, opts) {
85
+ const usage = msg?.usage;
86
+ if (!usage) return null;
87
+ const runId = resolveRunId(opts, msg.id);
88
+ return {
89
+ customerId: opts.customerId,
90
+ agentId: opts.agentId,
91
+ runId,
92
+ provider: "anthropic",
93
+ model: msg.model ?? "unknown",
94
+ tokensIn: usage.input_tokens ?? 0,
95
+ tokensOut: usage.output_tokens ?? 0,
96
+ cachedTokens: usage.cache_read_input_tokens ?? 0,
97
+ revenueUnitCents: resolveRevenue(opts, msg),
98
+ idempotencyKey: msg.id ?? `${runId}#${randomId()}`
99
+ };
100
+ }
101
+ function wrapAnthropic(client, tollgate, opts) {
102
+ const messages = client.messages;
103
+ const original = messages.create.bind(messages);
104
+ const create = async (...args) => {
105
+ const result = await original(...args);
106
+ const event = anthropicEventFrom(result, opts);
107
+ if (event) fireAndForget(tollgate.track(event), opts.onError);
108
+ return result;
109
+ };
110
+ return new Proxy(client, {
111
+ get(target, prop, recv) {
112
+ if (prop === "messages") {
113
+ return new Proxy(messages, {
114
+ get: (m, p, r) => p === "create" ? create : Reflect.get(m, p, r)
115
+ });
116
+ }
117
+ return Reflect.get(target, prop, recv);
118
+ }
119
+ });
120
+ }
121
+ function openAIEventFrom(completion, opts) {
122
+ const usage = completion?.usage;
123
+ if (!usage) return null;
124
+ const runId = resolveRunId(opts, completion.id);
125
+ return {
126
+ customerId: opts.customerId,
127
+ agentId: opts.agentId,
128
+ runId,
129
+ provider: "openai",
130
+ model: completion.model ?? "unknown",
131
+ tokensIn: usage.prompt_tokens ?? 0,
132
+ tokensOut: usage.completion_tokens ?? 0,
133
+ reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? 0,
134
+ cachedTokens: usage.prompt_tokens_details?.cached_tokens ?? 0,
135
+ revenueUnitCents: resolveRevenue(opts, completion),
136
+ idempotencyKey: completion.id ?? `${runId}#${randomId()}`
137
+ };
138
+ }
139
+ function wrapOpenAI(client, tollgate, opts) {
140
+ const completions = client.chat.completions;
141
+ const original = completions.create.bind(completions);
142
+ const create = async (...args) => {
143
+ const result = await original(...args);
144
+ const event = openAIEventFrom(result, opts);
145
+ if (event) fireAndForget(tollgate.track(event), opts.onError);
146
+ return result;
147
+ };
148
+ return new Proxy(client, {
149
+ get(target, prop, recv) {
150
+ if (prop === "chat") {
151
+ const chat = Reflect.get(target, prop, recv);
152
+ return new Proxy(chat, {
153
+ get: (c, p, r) => p === "completions" ? new Proxy(completions, {
154
+ get: (co, pp, rr) => pp === "create" ? create : Reflect.get(co, pp, rr)
155
+ }) : Reflect.get(c, p, r)
156
+ });
157
+ }
158
+ return Reflect.get(target, prop, recv);
159
+ }
160
+ });
161
+ }
162
+
163
+ export { TollgateError, anthropicEventFrom, createTollgateClient, openAIEventFrom, wrapAnthropic, wrapOpenAI };
164
+ //# sourceMappingURL=index.js.map
165
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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,gCAAA;AAOzB,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,OAAO,EAAE,KAAA,EAAM;AACjB;;;ACpFA,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;AAgBO,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;AACvC,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,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 { 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://tollgateapp.vercel.app';\n\nexport interface TollgateClient {\n /** Report a single usage event. Idempotent on `idempotencyKey`. */\n track(event: TrackEventInput): 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 return { track };\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}\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 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 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"]}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@tollgateai/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Track real LLM model usage and compute live gross margin with Tollgate.",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "sideEffects": false,
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "typecheck": "tsc --noEmit",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "keywords": [
28
+ "llm",
29
+ "tokens",
30
+ "cost",
31
+ "margin",
32
+ "observability",
33
+ "anthropic",
34
+ "openai",
35
+ "tollgate"
36
+ ],
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "devDependencies": {
41
+ "tsup": "^8.3.5",
42
+ "typescript": "^5.7.3"
43
+ }
44
+ }