@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 +103 -0
- package/dist/index.cjs +172 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +116 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.js +165 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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":"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","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
|
+
}
|