tollgate-skill 1.0.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/.claude-plugin/marketplace.json +18 -0
- package/.claude-plugin/plugin.json +8 -0
- package/README.md +114 -0
- package/bin/tollgate-skill.js +88 -0
- package/claude-code/SKILL.md +331 -0
- package/codex/AGENTS.md +150 -0
- package/copilot/copilot-instructions.md +137 -0
- package/cursor/tollgate.mdc +141 -0
- package/install.sh +86 -0
- package/package.json +42 -0
- package/windsurf/tollgate.md +123 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tollgate",
|
|
3
|
+
"owner": { "name": "Tollgate", "url": "https://tollgateai.com" },
|
|
4
|
+
"plugins": [
|
|
5
|
+
{
|
|
6
|
+
"name": "tollgate",
|
|
7
|
+
"description": "Real-time gross-margin observability for AI agents. Wraps any LLM provider with cost + margin tracking in 2 lines.",
|
|
8
|
+
"category": "development",
|
|
9
|
+
"source": {
|
|
10
|
+
"source": "git",
|
|
11
|
+
"url": "https://github.com/Tollgateai/tollgate-skill.git",
|
|
12
|
+
"ref": "main"
|
|
13
|
+
},
|
|
14
|
+
"skills": ["./claude-code"],
|
|
15
|
+
"homepage": "https://github.com/Tollgateai/tollgate-skill"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tollgate",
|
|
3
|
+
"description": "Real-time gross-margin observability for AI agents. Wraps any LLM provider with cost + margin tracking in 2 lines.",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"author": { "name": "Tollgate", "url": "https://tollgateai.com" },
|
|
6
|
+
"keywords": ["ai", "cost", "margin", "observability", "llm", "agents"],
|
|
7
|
+
"homepage": "https://github.com/Tollgateai/tollgate-skill"
|
|
8
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# tollgate-skill
|
|
2
|
+
|
|
3
|
+
> AI coding tool instructions for integrating [Tollgate](https://www.tollgateai.dev) — real-time gross-margin observability for AI products.
|
|
4
|
+
|
|
5
|
+
Works with **Claude Code**, **Cursor**, **GitHub Copilot**, **Windsurf**, and **Codex**. One install, your AI coding assistant knows how to wire up Tollgate correctly.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What this does
|
|
10
|
+
|
|
11
|
+
Once installed, your AI coding assistant can:
|
|
12
|
+
- Wrap any LLM provider client (Anthropic, OpenAI, Gemini, Bedrock, OpenRouter…) with Tollgate tracking in 2 lines
|
|
13
|
+
- Set `idempotencyKey`, `reasoningTokens`, `cachedTokens` correctly
|
|
14
|
+
- Handle multi-step agents and run outcomes
|
|
15
|
+
- Configure streaming correctly for each provider
|
|
16
|
+
- Keep prompt content out of the tracking payload
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
### Claude Code
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
curl -fsSL https://raw.githubusercontent.com/Tollgateai/tollgate-skill/main/install.sh | bash -s -- --tool claude
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Installs to `~/.claude/skills/tollgate/SKILL.md`. Restart Claude Code, then type `/tollgate` to activate.
|
|
29
|
+
|
|
30
|
+
### Cursor
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
curl -fsSL https://raw.githubusercontent.com/Tollgateai/tollgate-skill/main/install.sh | bash -s -- --tool cursor
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Installs to `.cursor/rules/tollgate.mdc` in your project. Cursor picks it up automatically when relevant.
|
|
37
|
+
|
|
38
|
+
### GitHub Copilot
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
curl -fsSL https://raw.githubusercontent.com/Tollgateai/tollgate-skill/main/install.sh | bash -s -- --tool copilot
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Appends to (or creates) `.github/copilot-instructions.md` in your project.
|
|
45
|
+
|
|
46
|
+
### Windsurf
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
curl -fsSL https://raw.githubusercontent.com/Tollgateai/tollgate-skill/main/install.sh | bash -s -- --tool windsurf
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Installs to `.windsurf/rules/tollgate.md` in your project.
|
|
53
|
+
|
|
54
|
+
### Codex
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
curl -fsSL https://raw.githubusercontent.com/Tollgateai/tollgate-skill/main/install.sh | bash -s -- --tool codex
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Appends to (or creates) `AGENTS.md` in your project.
|
|
61
|
+
|
|
62
|
+
### All tools at once
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
curl -fsSL https://raw.githubusercontent.com/Tollgateai/tollgate-skill/main/install.sh | bash -s -- --tool all
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Manual install
|
|
71
|
+
|
|
72
|
+
| Tool | Copy this file to... |
|
|
73
|
+
|---|---|
|
|
74
|
+
| Claude Code | `~/.claude/skills/tollgate/SKILL.md` |
|
|
75
|
+
| Cursor | `.cursor/rules/tollgate.mdc` |
|
|
76
|
+
| GitHub Copilot | `.github/copilot-instructions.md` |
|
|
77
|
+
| Windsurf | `.windsurf/rules/tollgate.md` |
|
|
78
|
+
| Codex | `AGENTS.md` (project root) |
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## What you'll need
|
|
83
|
+
|
|
84
|
+
- A Tollgate account — [sign up at tollgateai](https://www.tollgateai.dev)
|
|
85
|
+
- An API key from **Settings → API Keys** (prefix: `tg_live_…`)
|
|
86
|
+
- The Tollgate SDK: `npm install @tollgateai/sdk` or `pip install tollgateai`
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Repo structure
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
tollgate-skill/
|
|
94
|
+
claude-code/
|
|
95
|
+
SKILL.md Claude Code skill (YAML frontmatter + full integration guide)
|
|
96
|
+
cursor/
|
|
97
|
+
tollgate.mdc Cursor rule (.mdc format with glob config)
|
|
98
|
+
copilot/
|
|
99
|
+
copilot-instructions.md GitHub Copilot workspace instructions
|
|
100
|
+
windsurf/
|
|
101
|
+
tollgate.md Windsurf rule
|
|
102
|
+
codex/
|
|
103
|
+
AGENTS.md OpenAI Codex agent instructions
|
|
104
|
+
install.sh One-liner installer for all tools
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Links
|
|
110
|
+
|
|
111
|
+
- [Tollgate Dashboard](https://www.tollgateai.dev/dashboard)
|
|
112
|
+
- [SDK — @tollgateai/sdk](https://www.npmjs.com/package/@tollgateai/sdk) · [source](https://github.com/Tollgateai/tollgate-sdk/tree/main/packages/tollgate-sdk-ts)
|
|
113
|
+
- [Python SDK — tollgateai](https://pypi.org/project/tollgateai/) · [source](https://github.com/Tollgateai/tollgate-sdk/tree/main/packages/tollgate-sdk-python)
|
|
114
|
+
- [Docs](https://www.tollgateai.dev/docs)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const toolFlagIndex = args.indexOf('--tool');
|
|
9
|
+
const tool = toolFlagIndex !== -1 ? args[toolFlagIndex + 1] : (args[0] || '');
|
|
10
|
+
|
|
11
|
+
const ROOT = path.join(__dirname, '..');
|
|
12
|
+
|
|
13
|
+
function copyFile(src, dest) {
|
|
14
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
15
|
+
fs.copyFileSync(src, dest);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function appendOrCreate(src, dest) {
|
|
19
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
20
|
+
const content = fs.readFileSync(src, 'utf8');
|
|
21
|
+
if (fs.existsSync(dest)) {
|
|
22
|
+
fs.appendFileSync(dest, '\n' + content);
|
|
23
|
+
return 'appended to';
|
|
24
|
+
}
|
|
25
|
+
fs.writeFileSync(dest, content);
|
|
26
|
+
return 'created at';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function installClaude() {
|
|
30
|
+
const dest = path.join(os.homedir(), '.claude', 'skills', 'tollgate', 'SKILL.md');
|
|
31
|
+
copyFile(path.join(ROOT, 'claude-code', 'SKILL.md'), dest);
|
|
32
|
+
console.log(`Claude Code: skill installed at ${dest}`);
|
|
33
|
+
console.log(' Restart Claude Code, then use /tollgate to activate.');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function installCursor() {
|
|
37
|
+
const dest = path.join(process.cwd(), '.cursor', 'rules', 'tollgate.mdc');
|
|
38
|
+
copyFile(path.join(ROOT, 'cursor', 'tollgate.mdc'), dest);
|
|
39
|
+
console.log(`Cursor: rule installed at ${dest}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function installCopilot() {
|
|
43
|
+
const dest = path.join(process.cwd(), '.github', 'copilot-instructions.md');
|
|
44
|
+
const action = appendOrCreate(path.join(ROOT, 'copilot', 'copilot-instructions.md'), dest);
|
|
45
|
+
console.log(`Copilot: instructions ${action} ${dest}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function installWindsurf() {
|
|
49
|
+
const dest = path.join(process.cwd(), '.windsurf', 'rules', 'tollgate.md');
|
|
50
|
+
copyFile(path.join(ROOT, 'windsurf', 'tollgate.md'), dest);
|
|
51
|
+
console.log(`Windsurf: rule installed at ${dest}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function installCodex() {
|
|
55
|
+
const dest = path.join(process.cwd(), 'AGENTS.md');
|
|
56
|
+
const action = appendOrCreate(path.join(ROOT, 'codex', 'AGENTS.md'), dest);
|
|
57
|
+
console.log(`Codex: AGENTS.md ${action} ${dest}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function usage() {
|
|
61
|
+
console.log('Tollgate skill installer\n');
|
|
62
|
+
console.log('Usage:');
|
|
63
|
+
console.log(' npx tollgate-skill --tool claude # Claude Code (~/.claude/skills/tollgate/)');
|
|
64
|
+
console.log(' npx tollgate-skill --tool cursor # Cursor (.cursor/rules/tollgate.mdc)');
|
|
65
|
+
console.log(' npx tollgate-skill --tool copilot # GitHub Copilot (.github/copilot-instructions.md)');
|
|
66
|
+
console.log(' npx tollgate-skill --tool windsurf # Windsurf (.windsurf/rules/tollgate.md)');
|
|
67
|
+
console.log(' npx tollgate-skill --tool codex # Codex (AGENTS.md)');
|
|
68
|
+
console.log(' npx tollgate-skill --tool all # All of the above');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
switch (tool) {
|
|
72
|
+
case 'claude': installClaude(); break;
|
|
73
|
+
case 'cursor': installCursor(); break;
|
|
74
|
+
case 'copilot': installCopilot(); break;
|
|
75
|
+
case 'windsurf': installWindsurf(); break;
|
|
76
|
+
case 'codex': installCodex(); break;
|
|
77
|
+
case 'all':
|
|
78
|
+
installClaude();
|
|
79
|
+
installCursor();
|
|
80
|
+
installCopilot();
|
|
81
|
+
installWindsurf();
|
|
82
|
+
installCodex();
|
|
83
|
+
break;
|
|
84
|
+
default:
|
|
85
|
+
usage();
|
|
86
|
+
console.log('\nDefaulting to Claude Code:');
|
|
87
|
+
installClaude();
|
|
88
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tollgate
|
|
3
|
+
description: Integrate Tollgate to track LLM costs per customer and monitor gross margin in real time. Use when the user wants to add Tollgate to their AI product.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Tollgate Integration Skill
|
|
8
|
+
|
|
9
|
+
Tollgate is a **real-time gross-margin observability** layer for AI products. It joins LLM provider costs with your revenue config to show per-customer, per-agent, per-run margins — so you know which customers are profitable and which are not.
|
|
10
|
+
|
|
11
|
+
> "Stripe/Orb tells you what to charge. Tollgate tells you if you're making money."
|
|
12
|
+
|
|
13
|
+
## When to use this skill
|
|
14
|
+
|
|
15
|
+
Trigger on phrases like:
|
|
16
|
+
- "integrate Tollgate", "add Tollgate tracking", "set up cost tracking"
|
|
17
|
+
- "track LLM costs per customer", "monitor gross margin"
|
|
18
|
+
- "instrument my AI agent with Tollgate"
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## What Tollgate tracks
|
|
23
|
+
|
|
24
|
+
Each call to `POST /api/track` records one **usage event** — a single LLM call, tool call, or retrieval step — attributed to a customer and run. Tollgate:
|
|
25
|
+
|
|
26
|
+
1. Writes the raw event to DynamoDB (idempotent, ~1 ms hot path)
|
|
27
|
+
2. Looks up the provider rate card to compute cost in cents
|
|
28
|
+
3. Looks up the customer's plan to compute recognized revenue
|
|
29
|
+
4. Rolls up `costCents`, `revenueCents`, `marginCents`, `marginPct` per customer/day/run
|
|
30
|
+
|
|
31
|
+
The dashboard shows live per-customer gross margin, margin-leak flags, and trend charts.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Quick start (3 steps)
|
|
36
|
+
|
|
37
|
+
### Step 1 — Get an API key
|
|
38
|
+
|
|
39
|
+
Generate a key in **Settings → API Keys** (prefix: `tg_live_…`). Set it as an environment variable:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
TOLLGATE_API_KEY=tg_live_your_key_here
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Step 2 — Install the SDK
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# TypeScript / Node.js
|
|
49
|
+
npm install @tollgateai/sdk
|
|
50
|
+
# or: pnpm add @tollgateai/sdk | yarn add @tollgateai/sdk
|
|
51
|
+
|
|
52
|
+
# Python
|
|
53
|
+
pip install tollgateai
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Step 3 — Wrap your provider client
|
|
57
|
+
|
|
58
|
+
Pick the snippet for your provider. Tollgate intercepts the response, extracts token counts, and fires `POST /api/track` in the background.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Provider integration examples
|
|
63
|
+
|
|
64
|
+
### Anthropic (TypeScript)
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
68
|
+
import { createTollgateClient, wrapAnthropic } from '@tollgateai/sdk';
|
|
69
|
+
|
|
70
|
+
const tollgate = createTollgateClient(); // reads TOLLGATE_API_KEY
|
|
71
|
+
const anthropic = wrapAnthropic(new Anthropic(), tollgate, {
|
|
72
|
+
customerId: 'cust_acme', // your external customer id — required
|
|
73
|
+
runId: 'ticket_8842', // your run/session id — recommended
|
|
74
|
+
agentId: 'support-agent', // optional: which agent within the run
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Use anthropic exactly as before — no changes needed below this line.
|
|
78
|
+
const msg = await anthropic.messages.create({
|
|
79
|
+
model: 'claude-opus-4-8',
|
|
80
|
+
max_tokens: 1024,
|
|
81
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### OpenAI (TypeScript)
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import OpenAI from 'openai';
|
|
89
|
+
import { createTollgateClient, wrapOpenAI } from '@tollgateai/sdk';
|
|
90
|
+
|
|
91
|
+
const tollgate = createTollgateClient();
|
|
92
|
+
const openai = wrapOpenAI(new OpenAI(), tollgate, {
|
|
93
|
+
customerId: 'cust_acme',
|
|
94
|
+
runId: 'ticket_8842',
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### OpenAI-compatible gateways (Groq, OpenRouter, Vercel AI Gateway, vLLM, Together, Fireworks)
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import OpenAI from 'openai';
|
|
102
|
+
import { createTollgateClient, wrapOpenAI } from '@tollgateai/sdk';
|
|
103
|
+
|
|
104
|
+
const tollgate = createTollgateClient();
|
|
105
|
+
const groq = wrapOpenAI(
|
|
106
|
+
new OpenAI({ baseURL: 'https://api.groq.com/openai/v1', apiKey: process.env.GROQ_API_KEY }),
|
|
107
|
+
tollgate,
|
|
108
|
+
{ customerId: 'cust_acme', runId: 'ticket_8842', provider: 'openai_compatible' },
|
|
109
|
+
);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Google Gemini (TypeScript)
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { GoogleGenAI } from '@google/genai';
|
|
116
|
+
import { createTollgateClient, wrapGemini } from '@tollgateai/sdk';
|
|
117
|
+
|
|
118
|
+
const tollgate = createTollgateClient();
|
|
119
|
+
const gemini = wrapGemini(new GoogleGenAI({ apiKey: process.env.GOOGLE_API_KEY }), tollgate, {
|
|
120
|
+
customerId: 'cust_acme',
|
|
121
|
+
runId: 'ticket_8842',
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### AWS Bedrock (TypeScript)
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { BedrockRuntimeClient } from '@aws-sdk/client-bedrock-runtime';
|
|
129
|
+
import { createTollgateClient, wrapBedrock } from '@tollgateai/sdk';
|
|
130
|
+
|
|
131
|
+
const tollgate = createTollgateClient();
|
|
132
|
+
const bedrock = wrapBedrock(new BedrockRuntimeClient({ region: 'us-east-1' }), tollgate, {
|
|
133
|
+
customerId: 'cust_acme',
|
|
134
|
+
runId: 'ticket_8842',
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Anthropic (Python)
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
import anthropic
|
|
142
|
+
from tollgate import create_tollgate_client, wrap_anthropic
|
|
143
|
+
|
|
144
|
+
tollgate = create_tollgate_client() # reads TOLLGATE_API_KEY
|
|
145
|
+
client = wrap_anthropic(
|
|
146
|
+
anthropic.Anthropic(), tollgate,
|
|
147
|
+
customer_id="cust_acme",
|
|
148
|
+
run_id="ticket_8842",
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### OpenAI (Python)
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from openai import OpenAI
|
|
156
|
+
from tollgate import create_tollgate_client, wrap_openai
|
|
157
|
+
|
|
158
|
+
tollgate = create_tollgate_client()
|
|
159
|
+
client = wrap_openai(OpenAI(), tollgate, customer_id="cust_acme", run_id="ticket_8842")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Streaming
|
|
165
|
+
|
|
166
|
+
The wrapper handles streaming automatically. **OpenAI / OpenAI-compatible only:** add `stream_options: { include_usage: true }` or token counts won't be captured.
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
const stream = await openai.chat.completions.create({
|
|
170
|
+
model: 'gpt-4o',
|
|
171
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
172
|
+
stream: true,
|
|
173
|
+
stream_options: { include_usage: true }, // required for Tollgate
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Multi-step agents — closing a run
|
|
180
|
+
|
|
181
|
+
A **run** (`runId`) groups all LLM calls for one end-to-end task. Set `outcome` on the final step to close the run and gate outcome-priced revenue.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
await tollgate.resolve({
|
|
185
|
+
runId: 'ticket_8842',
|
|
186
|
+
customerId: 'cust_acme',
|
|
187
|
+
outcome: 'resolved', // 'resolved' | 'escalated' | 'failed'
|
|
188
|
+
revenueUnitCents: 50, // $0.50 per resolved ticket
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- `resolved` — task done; outcome-priced revenue is recognized.
|
|
193
|
+
- `escalated` / `failed` — no activity revenue recognized, but all costs are tracked. Makes unprofitable runs visible.
|
|
194
|
+
- Omitting `outcome` on a single-call run treats it as `resolved`.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Manual tracking (no wrapper)
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
await tollgate.track({
|
|
202
|
+
customerId: 'cust_acme',
|
|
203
|
+
runId: 'ticket_8842',
|
|
204
|
+
provider: 'anthropic', // 'anthropic' | 'openai' | 'openai_compatible' | 'bedrock' | 'google'
|
|
205
|
+
model: 'claude-opus-4-8',
|
|
206
|
+
tokensIn: response.usage.input_tokens,
|
|
207
|
+
tokensOut: response.usage.output_tokens,
|
|
208
|
+
reasoningTokens: response.usage.cache_creation_input_tokens ?? 0,
|
|
209
|
+
cachedTokens: response.usage.cache_read_input_tokens ?? 0,
|
|
210
|
+
idempotencyKey: `${runId}#step_1`, // stable, unique per event
|
|
211
|
+
latencyMs: Date.now() - startTime,
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### REST
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
curl -X POST https://www.tollgateai.dev/api/track \
|
|
219
|
+
-H "Authorization: Bearer tg_live_your_key" \
|
|
220
|
+
-H "Content-Type: application/json" \
|
|
221
|
+
-d '{
|
|
222
|
+
"customerId": "cust_acme",
|
|
223
|
+
"runId": "ticket_8842",
|
|
224
|
+
"provider": "anthropic",
|
|
225
|
+
"model": "claude-opus-4-8",
|
|
226
|
+
"tokensIn": 1200,
|
|
227
|
+
"tokensOut": 340,
|
|
228
|
+
"idempotencyKey": "ticket_8842#step_1"
|
|
229
|
+
}'
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Response: `{ "status": "created", "eventId": "..." }` or `{ "status": "duplicate" }` (safe to ignore).
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Full field reference
|
|
237
|
+
|
|
238
|
+
| Field | Type | Required | Description |
|
|
239
|
+
|---|---|---|---|
|
|
240
|
+
| `customerId` | string | ✅ | Your external customer id. |
|
|
241
|
+
| `runId` | string | ✅ | Groups all LLM calls for one task/session. |
|
|
242
|
+
| `provider` | string | ✅ | `anthropic` `openai` `openai_compatible` `bedrock` `google` |
|
|
243
|
+
| `model` | string | ✅ | Model name as returned by provider. |
|
|
244
|
+
| `idempotencyKey` | string | ✅ | Unique per event — prevents double-counting on retries. Pattern: `runId#step_N`. |
|
|
245
|
+
| `agentId` | string | — | Which agent within the run. |
|
|
246
|
+
| `type` | string | — | `llm` (default) `tool` `retrieval` |
|
|
247
|
+
| `tokensIn` | int | — | Standard (non-cached) input tokens. |
|
|
248
|
+
| `tokensOut` | int | — | Output tokens. |
|
|
249
|
+
| `reasoningTokens` | int | — | Thinking/reasoning tokens — billed at output rate. |
|
|
250
|
+
| `cachedTokens` | int | — | Cache-read input tokens — billed at reduced rate. |
|
|
251
|
+
| `cacheWrite5mTokens` | int | — | Cache-write tokens, 5-min TTL. |
|
|
252
|
+
| `cacheWrite1hTokens` | int | — | Cache-write tokens, 1-hour TTL. |
|
|
253
|
+
| `toolCalls` | int | — | Number of tool calls. |
|
|
254
|
+
| `toolName` | string | — | Tool name for per-tool breakdown. |
|
|
255
|
+
| `audioTokensIn` | int | — | Audio input tokens (OpenAI Realtime). |
|
|
256
|
+
| `audioTokensOut` | int | — | Audio output tokens. |
|
|
257
|
+
| `imageTokensIn` | int | — | Image/vision input tokens. |
|
|
258
|
+
| `imageTokensOut` | int | — | Image generation output tokens. |
|
|
259
|
+
| `videoTokensIn` | int | — | Video input tokens (Gemini). |
|
|
260
|
+
| `webSearchRequests` | int | — | Web search calls (Anthropic/Gemini grounding). |
|
|
261
|
+
| `latencyMs` | int | — | Request latency in milliseconds. |
|
|
262
|
+
| `externalCostCents` | float | — | Cost of external tools used (image gen APIs, sandboxes). Added directly to cost. |
|
|
263
|
+
| `providerCostCents` | float | — | Exact cost from provider/gateway — skips rate-card lookup. |
|
|
264
|
+
| `outcome` | string | — | `resolved` `escalated` `failed` — set only on the closing event. |
|
|
265
|
+
| `revenueUnitCents` | int | — | Per-run revenue in cents. Overrides plan default. |
|
|
266
|
+
| `ts` | ISO string | — | Event timestamp. Defaults to server receive time. |
|
|
267
|
+
|
|
268
|
+
> **Privacy:** Never send prompt content. Fields like `prompt`, `messages`, `content`, `input`, `output` are rejected with HTTP 400.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Error codes
|
|
273
|
+
|
|
274
|
+
| Status | Meaning |
|
|
275
|
+
|---|---|
|
|
276
|
+
| 201 created | Event ingested. |
|
|
277
|
+
| 200 duplicate | Same `idempotencyKey` already stored — ignore. |
|
|
278
|
+
| 400 | Validation error or prompt content detected. |
|
|
279
|
+
| 401 | Invalid or missing API key. |
|
|
280
|
+
| 402 | Monthly event quota reached. |
|
|
281
|
+
| 429 | Rate limit exceeded — check `Retry-After` header. |
|
|
282
|
+
| 500 | Internal error — event may not have been stored. |
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Register a customer before first usage (optional but recommended)
|
|
287
|
+
|
|
288
|
+
Call `upsertCustomer()` before sending any usage events so the plan is ready and revenue is recognized from event one. This is especially important for `usage_based` pricing, where revenue is computed at ingest time.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
await tollgate.upsertCustomer({
|
|
292
|
+
externalId: 'cust_acme', // must match the customerId used in track/wrap
|
|
293
|
+
name: 'Acme Corp',
|
|
294
|
+
plan: {
|
|
295
|
+
name: 'Growth',
|
|
296
|
+
pricingModel: 'per_unit', // 'per_unit' | 'per_resolution' | 'usage_based' | 'per_seat' | 'flat' | 'hybrid'
|
|
297
|
+
unitRevenueCents: 50, // $0.50 per resolved ticket
|
|
298
|
+
baseRevenueCents: 0,
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Integration checklist
|
|
306
|
+
|
|
307
|
+
- [ ] `TOLLGATE_API_KEY` set in environment (never commit it)
|
|
308
|
+
- [ ] Provider client wrapped (or `tollgate.track()` called after each LLM call)
|
|
309
|
+
- [ ] `customerId` matches real customer ids in your system
|
|
310
|
+
- [ ] `runId` consistently identifies one end-to-end task
|
|
311
|
+
- [ ] `idempotencyKey` is stable and unique per event
|
|
312
|
+
- [ ] `reasoningTokens` included for extended thinking models (Claude 3.7+, o1, o3)
|
|
313
|
+
- [ ] `cachedTokens` included if using prompt caching
|
|
314
|
+
- [ ] `outcome` set on the closing event for per_unit / hybrid plans
|
|
315
|
+
- [ ] No prompt content in any field
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Implementation approach
|
|
320
|
+
|
|
321
|
+
When helping a user integrate Tollgate:
|
|
322
|
+
|
|
323
|
+
1. Ask which provider(s) they use (Anthropic / OpenAI / Gemini / Bedrock / gateway).
|
|
324
|
+
2. Ask which language (TypeScript / Python / other).
|
|
325
|
+
3. Find where they create the provider client — show the 2-line wrap.
|
|
326
|
+
4. If they have multi-step agents, identify the final step and add `resolve()`.
|
|
327
|
+
5. If they use OpenAI streaming, add `stream_options: { include_usage: true }`.
|
|
328
|
+
6. Confirm `customerId` maps to real customer ids in their system.
|
|
329
|
+
7. Remind them to add `TOLLGATE_API_KEY` to `.env` and `.gitignore`.
|
|
330
|
+
|
|
331
|
+
The wrapper never modifies the response the user's code sees, never blocks on Tollgate errors, and always passes through to the original provider.
|
package/codex/AGENTS.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Tollgate Integration
|
|
2
|
+
|
|
3
|
+
When asked to integrate Tollgate, add LLM cost tracking, or set up gross-margin observability, follow these instructions.
|
|
4
|
+
|
|
5
|
+
Tollgate tracks LLM provider costs per customer in real time and joins them with revenue config to show gross margin per customer, agent, and run.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
Set the API key (get it from Settings → API Keys on the Tollgate dashboard):
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
TOLLGATE_API_KEY=tg_live_your_key_here
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Install the SDK:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @tollgateai/sdk # TypeScript / Node.js
|
|
19
|
+
pip install tollgateai # Python
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Wrap the provider client
|
|
23
|
+
|
|
24
|
+
Find where the user creates their provider client. Replace the client with a wrapped version. The wrapper is transparent — it never changes the response shape or blocks on errors.
|
|
25
|
+
|
|
26
|
+
**Anthropic (TypeScript):**
|
|
27
|
+
```typescript
|
|
28
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
29
|
+
import { createTollgateClient, wrapAnthropic } from '@tollgateai/sdk';
|
|
30
|
+
|
|
31
|
+
const tollgate = createTollgateClient();
|
|
32
|
+
const anthropic = wrapAnthropic(new Anthropic(), tollgate, {
|
|
33
|
+
customerId: 'cust_acme',
|
|
34
|
+
runId: 'ticket_8842',
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**OpenAI (TypeScript):**
|
|
39
|
+
```typescript
|
|
40
|
+
import OpenAI from 'openai';
|
|
41
|
+
import { createTollgateClient, wrapOpenAI } from '@tollgateai/sdk';
|
|
42
|
+
|
|
43
|
+
const tollgate = createTollgateClient();
|
|
44
|
+
const openai = wrapOpenAI(new OpenAI(), tollgate, { customerId: 'cust_acme', runId: 'ticket_8842' });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**OpenAI-compatible (Groq, OpenRouter, vLLM, Together, Fireworks):**
|
|
48
|
+
```typescript
|
|
49
|
+
const client = wrapOpenAI(
|
|
50
|
+
new OpenAI({ baseURL: 'https://api.groq.com/openai/v1', apiKey: process.env.GROQ_API_KEY }),
|
|
51
|
+
tollgate,
|
|
52
|
+
{ customerId: 'cust_acme', runId: 'ticket_8842', provider: 'openai_compatible' },
|
|
53
|
+
);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Google Gemini (TypeScript):**
|
|
57
|
+
```typescript
|
|
58
|
+
import { GoogleGenAI } from '@google/genai';
|
|
59
|
+
import { createTollgateClient, wrapGemini } from '@tollgateai/sdk';
|
|
60
|
+
const gemini = wrapGemini(new GoogleGenAI({ apiKey: process.env.GOOGLE_API_KEY }), tollgate, { customerId: 'cust_acme', runId: 'ticket_8842' });
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**AWS Bedrock (TypeScript):**
|
|
64
|
+
```typescript
|
|
65
|
+
import { BedrockRuntimeClient } from '@aws-sdk/client-bedrock-runtime';
|
|
66
|
+
import { createTollgateClient, wrapBedrock } from '@tollgateai/sdk';
|
|
67
|
+
const bedrock = wrapBedrock(new BedrockRuntimeClient({ region: 'us-east-1' }), tollgate, { customerId: 'cust_acme', runId: 'ticket_8842' });
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Anthropic (Python):**
|
|
71
|
+
```python
|
|
72
|
+
import anthropic
|
|
73
|
+
from tollgate import create_tollgate_client, wrap_anthropic
|
|
74
|
+
|
|
75
|
+
tollgate = create_tollgate_client()
|
|
76
|
+
client = wrap_anthropic(anthropic.Anthropic(), tollgate, customer_id="cust_acme", run_id="ticket_8842")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**OpenAI (Python):**
|
|
80
|
+
```python
|
|
81
|
+
from openai import OpenAI
|
|
82
|
+
from tollgate import create_tollgate_client, wrap_openai
|
|
83
|
+
|
|
84
|
+
tollgate = create_tollgate_client()
|
|
85
|
+
client = wrap_openai(OpenAI(), tollgate, customer_id="cust_acme", run_id="ticket_8842")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Streaming (OpenAI and OpenAI-compatible only)
|
|
89
|
+
|
|
90
|
+
Add `stream_options: { include_usage: true }` — otherwise Tollgate cannot see token counts during streaming.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const stream = await openai.chat.completions.create({
|
|
94
|
+
model: 'gpt-4o',
|
|
95
|
+
messages: [...],
|
|
96
|
+
stream: true,
|
|
97
|
+
stream_options: { include_usage: true },
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Closing a multi-step run
|
|
102
|
+
|
|
103
|
+
When a run has multiple LLM calls, call `resolve()` after the final step:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
await tollgate.resolve({
|
|
107
|
+
runId: 'ticket_8842',
|
|
108
|
+
customerId: 'cust_acme',
|
|
109
|
+
outcome: 'resolved', // 'resolved' | 'escalated' | 'failed'
|
|
110
|
+
revenueUnitCents: 50, // $0.50 per resolved ticket
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Use `escalated` or `failed` when the task did not complete successfully. Tollgate still tracks the cost but suppresses activity revenue, making the margin drag visible.
|
|
115
|
+
|
|
116
|
+
## Manual tracking (no wrapper available)
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
await tollgate.track({
|
|
120
|
+
customerId: 'cust_acme',
|
|
121
|
+
runId: 'ticket_8842',
|
|
122
|
+
provider: 'anthropic',
|
|
123
|
+
model: 'claude-opus-4-8',
|
|
124
|
+
tokensIn: 1200,
|
|
125
|
+
tokensOut: 340,
|
|
126
|
+
reasoningTokens: 0,
|
|
127
|
+
cachedTokens: 0,
|
|
128
|
+
idempotencyKey: `ticket_8842#step_1`,
|
|
129
|
+
latencyMs: 820,
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Rules to follow
|
|
134
|
+
|
|
135
|
+
- `idempotencyKey` must be stable and unique per event. Use `runId#step_N`. Duplicates are safely dropped.
|
|
136
|
+
- Never include prompt content in any field. Fields like `messages`, `content`, `input`, `output` are rejected with HTTP 400.
|
|
137
|
+
- Pass `reasoningTokens` separately — they bill at the output token rate.
|
|
138
|
+
- Pass `cachedTokens` separately — they bill at a reduced rate.
|
|
139
|
+
- Set `outcome` only on the closing event of a run.
|
|
140
|
+
|
|
141
|
+
## Error codes
|
|
142
|
+
|
|
143
|
+
- 201: event stored
|
|
144
|
+
- 200 duplicate: already stored, ignore
|
|
145
|
+
- 400: validation error or prompt content detected
|
|
146
|
+
- 401: bad API key
|
|
147
|
+
- 402: quota reached
|
|
148
|
+
- 429: rate limited — check Retry-After header
|
|
149
|
+
|
|
150
|
+
Full docs: https://www.tollgateai.dev/docs
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Tollgate Integration Instructions
|
|
2
|
+
|
|
3
|
+
When asked to integrate Tollgate, track LLM costs per customer, or add gross-margin observability, follow these instructions.
|
|
4
|
+
|
|
5
|
+
## What Tollgate does
|
|
6
|
+
|
|
7
|
+
Tollgate wraps your LLM provider client and tracks every API call — tokens in, tokens out, reasoning tokens, latency — and attributes them to a customer and run. It joins cost (from provider rate cards) with revenue (from your plan config) to show real-time gross margin per customer in a dashboard.
|
|
8
|
+
|
|
9
|
+
## Required setup
|
|
10
|
+
|
|
11
|
+
Get an API key from the Tollgate dashboard (Settings → API Keys). Add it to the environment:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
TOLLGATE_API_KEY=tg_live_your_key_here
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Install:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @tollgateai/sdk # TypeScript
|
|
21
|
+
pip install tollgateai # Python
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Wrap the provider client — TypeScript
|
|
25
|
+
|
|
26
|
+
**Anthropic:**
|
|
27
|
+
```typescript
|
|
28
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
29
|
+
import { createTollgateClient, wrapAnthropic } from '@tollgateai/sdk';
|
|
30
|
+
|
|
31
|
+
const tollgate = createTollgateClient();
|
|
32
|
+
const anthropic = wrapAnthropic(new Anthropic(), tollgate, {
|
|
33
|
+
customerId: 'cust_acme', // your external customer id — required
|
|
34
|
+
runId: 'ticket_8842', // groups all LLM calls for one task
|
|
35
|
+
});
|
|
36
|
+
// Use anthropic normally — tracking is automatic.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**OpenAI:**
|
|
40
|
+
```typescript
|
|
41
|
+
import OpenAI from 'openai';
|
|
42
|
+
import { createTollgateClient, wrapOpenAI } from '@tollgateai/sdk';
|
|
43
|
+
|
|
44
|
+
const tollgate = createTollgateClient();
|
|
45
|
+
const openai = wrapOpenAI(new OpenAI(), tollgate, { customerId: 'cust_acme', runId: 'ticket_8842' });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**OpenAI-compatible (Groq, OpenRouter, vLLM, Together, Fireworks):**
|
|
49
|
+
```typescript
|
|
50
|
+
const client = wrapOpenAI(
|
|
51
|
+
new OpenAI({ baseURL: 'https://api.groq.com/openai/v1', apiKey: process.env.GROQ_API_KEY }),
|
|
52
|
+
tollgate,
|
|
53
|
+
{ customerId: 'cust_acme', runId: 'ticket_8842', provider: 'openai_compatible' },
|
|
54
|
+
);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Google Gemini:**
|
|
58
|
+
```typescript
|
|
59
|
+
import { GoogleGenAI } from '@google/genai';
|
|
60
|
+
import { createTollgateClient, wrapGemini } from '@tollgateai/sdk';
|
|
61
|
+
const gemini = wrapGemini(new GoogleGenAI({ apiKey: process.env.GOOGLE_API_KEY }), tollgate, { customerId: 'cust_acme', runId: 'ticket_8842' });
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**AWS Bedrock:**
|
|
65
|
+
```typescript
|
|
66
|
+
import { BedrockRuntimeClient } from '@aws-sdk/client-bedrock-runtime';
|
|
67
|
+
import { createTollgateClient, wrapBedrock } from '@tollgateai/sdk';
|
|
68
|
+
const bedrock = wrapBedrock(new BedrockRuntimeClient({ region: 'us-east-1' }), tollgate, { customerId: 'cust_acme', runId: 'ticket_8842' });
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Wrap the provider client — Python
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from tollgate import create_tollgate_client, wrap_anthropic, wrap_openai
|
|
75
|
+
|
|
76
|
+
tollgate = create_tollgate_client()
|
|
77
|
+
|
|
78
|
+
# Anthropic
|
|
79
|
+
import anthropic
|
|
80
|
+
client = wrap_anthropic(anthropic.Anthropic(), tollgate, customer_id="cust_acme", run_id="ticket_8842")
|
|
81
|
+
|
|
82
|
+
# OpenAI
|
|
83
|
+
from openai import OpenAI
|
|
84
|
+
client = wrap_openai(OpenAI(), tollgate, customer_id="cust_acme", run_id="ticket_8842")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## OpenAI streaming — required flag
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const stream = await openai.chat.completions.create({
|
|
91
|
+
model: 'gpt-4o',
|
|
92
|
+
messages: [...],
|
|
93
|
+
stream: true,
|
|
94
|
+
stream_options: { include_usage: true }, // required — Tollgate won't see tokens without this
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Closing a multi-step run
|
|
99
|
+
|
|
100
|
+
After the final LLM call in a task, call resolve():
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
await tollgate.resolve({
|
|
104
|
+
runId: 'ticket_8842',
|
|
105
|
+
customerId: 'cust_acme',
|
|
106
|
+
outcome: 'resolved', // 'resolved' | 'escalated' | 'failed'
|
|
107
|
+
revenueUnitCents: 50, // cents — $0.50 per resolved ticket
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Important rules
|
|
112
|
+
|
|
113
|
+
- `idempotencyKey` must be unique per event (pattern: `runId#step_N`). Duplicates are safely ignored.
|
|
114
|
+
- Never send prompt content. Fields like `messages`, `content`, `input`, `output` are rejected.
|
|
115
|
+
- `reasoningTokens` (thinking/extended-thinking) bills at output rate — always pass separately.
|
|
116
|
+
- `cachedTokens` bills at reduced rate — always pass separately.
|
|
117
|
+
- `outcome` goes only on the final event of a run.
|
|
118
|
+
|
|
119
|
+
## Direct API call (any language)
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
POST https://www.tollgateai.dev/api/track
|
|
123
|
+
Authorization: Bearer tg_live_…
|
|
124
|
+
Content-Type: application/json
|
|
125
|
+
|
|
126
|
+
{
|
|
127
|
+
"customerId": "cust_acme",
|
|
128
|
+
"runId": "ticket_8842",
|
|
129
|
+
"provider": "anthropic",
|
|
130
|
+
"model": "claude-opus-4-8",
|
|
131
|
+
"tokensIn": 1200,
|
|
132
|
+
"tokensOut": 340,
|
|
133
|
+
"idempotencyKey": "ticket_8842#step_1"
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Full reference: https://www.tollgateai.dev/docs
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Instructions for integrating Tollgate LLM cost tracking and gross-margin observability into AI products. Apply when the user asks to add Tollgate, track LLM costs per customer, or monitor gross margin.
|
|
3
|
+
globs: []
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Tollgate Integration
|
|
8
|
+
|
|
9
|
+
Tollgate tracks LLM provider costs per customer and joins them with revenue to show real-time gross margin. One wrap of your provider client is all it takes.
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
TOLLGATE_API_KEY=tg_live_your_key_here
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @tollgateai/sdk # TypeScript
|
|
19
|
+
pip install tollgateai # Python
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Wrap your provider (TypeScript)
|
|
23
|
+
|
|
24
|
+
**Anthropic:**
|
|
25
|
+
```typescript
|
|
26
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
27
|
+
import { createTollgateClient, wrapAnthropic } from '@tollgateai/sdk';
|
|
28
|
+
|
|
29
|
+
const tollgate = createTollgateClient();
|
|
30
|
+
const anthropic = wrapAnthropic(new Anthropic(), tollgate, {
|
|
31
|
+
customerId: 'cust_acme', // required — your external customer id
|
|
32
|
+
runId: 'ticket_8842', // groups all calls for one task
|
|
33
|
+
});
|
|
34
|
+
// Use anthropic normally — Tollgate tracks in the background.
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**OpenAI:**
|
|
38
|
+
```typescript
|
|
39
|
+
import OpenAI from 'openai';
|
|
40
|
+
import { createTollgateClient, wrapOpenAI } from '@tollgateai/sdk';
|
|
41
|
+
|
|
42
|
+
const tollgate = createTollgateClient();
|
|
43
|
+
const openai = wrapOpenAI(new OpenAI(), tollgate, { customerId: 'cust_acme', runId: 'ticket_8842' });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**OpenAI-compatible gateways (Groq, OpenRouter, vLLM, Together):**
|
|
47
|
+
```typescript
|
|
48
|
+
const client = wrapOpenAI(
|
|
49
|
+
new OpenAI({ baseURL: 'https://api.groq.com/openai/v1', apiKey: process.env.GROQ_API_KEY }),
|
|
50
|
+
tollgate,
|
|
51
|
+
{ customerId: 'cust_acme', runId: 'ticket_8842', provider: 'openai_compatible' },
|
|
52
|
+
);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Google Gemini:**
|
|
56
|
+
```typescript
|
|
57
|
+
import { GoogleGenAI } from '@google/genai';
|
|
58
|
+
import { createTollgateClient, wrapGemini } from '@tollgateai/sdk';
|
|
59
|
+
const gemini = wrapGemini(new GoogleGenAI({ apiKey: process.env.GOOGLE_API_KEY }), tollgate, { customerId: 'cust_acme', runId: 'ticket_8842' });
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**AWS Bedrock:**
|
|
63
|
+
```typescript
|
|
64
|
+
import { BedrockRuntimeClient } from '@aws-sdk/client-bedrock-runtime';
|
|
65
|
+
import { createTollgateClient, wrapBedrock } from '@tollgateai/sdk';
|
|
66
|
+
const bedrock = wrapBedrock(new BedrockRuntimeClient({ region: 'us-east-1' }), tollgate, { customerId: 'cust_acme', runId: 'ticket_8842' });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Wrap your provider (Python)
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from tollgate import create_tollgate_client, wrap_anthropic, wrap_openai
|
|
73
|
+
|
|
74
|
+
tollgate = create_tollgate_client()
|
|
75
|
+
|
|
76
|
+
# Anthropic
|
|
77
|
+
import anthropic
|
|
78
|
+
client = wrap_anthropic(anthropic.Anthropic(), tollgate, customer_id="cust_acme", run_id="ticket_8842")
|
|
79
|
+
|
|
80
|
+
# OpenAI
|
|
81
|
+
from openai import OpenAI
|
|
82
|
+
client = wrap_openai(OpenAI(), tollgate, customer_id="cust_acme", run_id="ticket_8842")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Streaming (OpenAI only — required)
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const stream = await openai.chat.completions.create({
|
|
89
|
+
model: 'gpt-4o',
|
|
90
|
+
messages: [...],
|
|
91
|
+
stream: true,
|
|
92
|
+
stream_options: { include_usage: true }, // required for Tollgate to see token counts
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Close a multi-step run with outcome
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
await tollgate.resolve({
|
|
100
|
+
runId: 'ticket_8842',
|
|
101
|
+
customerId: 'cust_acme',
|
|
102
|
+
outcome: 'resolved', // 'resolved' | 'escalated' | 'failed'
|
|
103
|
+
revenueUnitCents: 50, // $0.50 per resolved ticket
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`escalated`/`failed` suppresses activity revenue but still tracks cost — making unprofitable runs visible on the dashboard.
|
|
108
|
+
|
|
109
|
+
## Manual tracking (no wrapper)
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
await tollgate.track({
|
|
113
|
+
customerId: 'cust_acme',
|
|
114
|
+
runId: 'ticket_8842',
|
|
115
|
+
provider: 'anthropic', // 'anthropic' | 'openai' | 'openai_compatible' | 'bedrock' | 'google'
|
|
116
|
+
model: 'claude-opus-4-8',
|
|
117
|
+
tokensIn: 1200,
|
|
118
|
+
tokensOut: 340,
|
|
119
|
+
reasoningTokens: 0, // thinking tokens — bill at output rate
|
|
120
|
+
cachedTokens: 0, // cache-read tokens — reduced rate
|
|
121
|
+
idempotencyKey: `${runId}#step_1`, // stable, unique per event
|
|
122
|
+
latencyMs: 820,
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Key rules
|
|
127
|
+
|
|
128
|
+
- `idempotencyKey` must be stable and unique per event (e.g. `runId#step_N`). Duplicates are silently dropped.
|
|
129
|
+
- Never send prompt content (`messages`, `content`, `input`, `output` etc.) — rejected with HTTP 400.
|
|
130
|
+
- `reasoningTokens` and `cachedTokens` must be passed separately — they bill at different rates.
|
|
131
|
+
- `outcome` goes only on the **closing** event of a run.
|
|
132
|
+
|
|
133
|
+
## REST endpoint
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
POST https://www.tollgateai.dev/api/track
|
|
137
|
+
Authorization: Bearer tg_live_…
|
|
138
|
+
Content-Type: application/json
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Full field reference: https://www.tollgateai.dev/docs
|
package/install.sh
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Tollgate skill installer
|
|
3
|
+
# Usage: curl -fsSL https://raw.githubusercontent.com/Tollgateai/tollgate-skill/main/install.sh | sh
|
|
4
|
+
# Or: bash install.sh [--tool claude|cursor|copilot|windsurf|codex|all]
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
REPO_URL="https://raw.githubusercontent.com/Tollgateai/tollgate-skill/main"
|
|
9
|
+
TOOL="${1:-}"
|
|
10
|
+
|
|
11
|
+
# Parse --tool flag
|
|
12
|
+
if [ "$1" = "--tool" ]; then
|
|
13
|
+
TOOL="$2"
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
install_claude_code() {
|
|
17
|
+
SKILL_DIR="${HOME}/.claude/skills/tollgate"
|
|
18
|
+
mkdir -p "$SKILL_DIR"
|
|
19
|
+
curl -fsSL "${REPO_URL}/claude-code/SKILL.md" -o "${SKILL_DIR}/SKILL.md"
|
|
20
|
+
echo "Claude Code: skill installed at ${SKILL_DIR}/SKILL.md"
|
|
21
|
+
echo " Restart Claude Code, then use /tollgate to activate the skill."
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
install_cursor() {
|
|
25
|
+
mkdir -p ".cursor/rules"
|
|
26
|
+
curl -fsSL "${REPO_URL}/cursor/tollgate.mdc" -o ".cursor/rules/tollgate.mdc"
|
|
27
|
+
echo "Cursor: rule installed at .cursor/rules/tollgate.mdc"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
install_copilot() {
|
|
31
|
+
mkdir -p ".github"
|
|
32
|
+
if [ -f ".github/copilot-instructions.md" ]; then
|
|
33
|
+
echo "" >> ".github/copilot-instructions.md"
|
|
34
|
+
curl -fsSL "${REPO_URL}/copilot/copilot-instructions.md" >> ".github/copilot-instructions.md"
|
|
35
|
+
echo "Copilot: appended to .github/copilot-instructions.md"
|
|
36
|
+
else
|
|
37
|
+
curl -fsSL "${REPO_URL}/copilot/copilot-instructions.md" -o ".github/copilot-instructions.md"
|
|
38
|
+
echo "Copilot: instructions created at .github/copilot-instructions.md"
|
|
39
|
+
fi
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
install_windsurf() {
|
|
43
|
+
mkdir -p ".windsurf/rules"
|
|
44
|
+
curl -fsSL "${REPO_URL}/windsurf/tollgate.md" -o ".windsurf/rules/tollgate.md"
|
|
45
|
+
echo "Windsurf: rule installed at .windsurf/rules/tollgate.md"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
install_codex() {
|
|
49
|
+
if [ -f "AGENTS.md" ]; then
|
|
50
|
+
echo "" >> "AGENTS.md"
|
|
51
|
+
curl -fsSL "${REPO_URL}/codex/AGENTS.md" >> "AGENTS.md"
|
|
52
|
+
echo "Codex: appended to AGENTS.md"
|
|
53
|
+
else
|
|
54
|
+
curl -fsSL "${REPO_URL}/codex/AGENTS.md" -o "AGENTS.md"
|
|
55
|
+
echo "Codex: AGENTS.md created"
|
|
56
|
+
fi
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
case "$TOOL" in
|
|
60
|
+
claude) install_claude_code ;;
|
|
61
|
+
cursor) install_cursor ;;
|
|
62
|
+
copilot) install_copilot ;;
|
|
63
|
+
windsurf) install_windsurf ;;
|
|
64
|
+
codex) install_codex ;;
|
|
65
|
+
all)
|
|
66
|
+
install_claude_code
|
|
67
|
+
install_cursor
|
|
68
|
+
install_copilot
|
|
69
|
+
install_windsurf
|
|
70
|
+
install_codex
|
|
71
|
+
;;
|
|
72
|
+
*)
|
|
73
|
+
echo "Tollgate skill installer"
|
|
74
|
+
echo ""
|
|
75
|
+
echo "Usage:"
|
|
76
|
+
echo " bash install.sh --tool claude # Claude Code (~/.claude/skills/tollgate/)"
|
|
77
|
+
echo " bash install.sh --tool cursor # Cursor (.cursor/rules/tollgate.mdc)"
|
|
78
|
+
echo " bash install.sh --tool copilot # GitHub Copilot (.github/copilot-instructions.md)"
|
|
79
|
+
echo " bash install.sh --tool windsurf # Windsurf (.windsurf/rules/tollgate.md)"
|
|
80
|
+
echo " bash install.sh --tool codex # Codex (AGENTS.md)"
|
|
81
|
+
echo " bash install.sh --tool all # All of the above"
|
|
82
|
+
echo ""
|
|
83
|
+
echo "Or install for Claude Code (most common):"
|
|
84
|
+
install_claude_code
|
|
85
|
+
;;
|
|
86
|
+
esac
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tollgate-skill",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI coding tool instructions for Tollgate — real-time gross-margin observability for AI agents",
|
|
5
|
+
"bin": {
|
|
6
|
+
"tollgate-skill": "./bin/tollgate-skill.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"claude-code/",
|
|
11
|
+
"cursor/",
|
|
12
|
+
"copilot/",
|
|
13
|
+
"windsurf/",
|
|
14
|
+
"codex/",
|
|
15
|
+
"install.sh",
|
|
16
|
+
".claude-plugin/"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"tollgate",
|
|
20
|
+
"llm",
|
|
21
|
+
"ai",
|
|
22
|
+
"cost",
|
|
23
|
+
"margin",
|
|
24
|
+
"observability",
|
|
25
|
+
"claude",
|
|
26
|
+
"cursor",
|
|
27
|
+
"copilot",
|
|
28
|
+
"windsurf",
|
|
29
|
+
"codex",
|
|
30
|
+
"agents"
|
|
31
|
+
],
|
|
32
|
+
"author": "Tollgate <hello@tollgateai.com>",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"homepage": "https://github.com/Tollgateai/tollgate-skill",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/Tollgateai/tollgate-skill.git"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=16"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Tollgate Integration
|
|
2
|
+
|
|
3
|
+
When the user asks to integrate Tollgate, add LLM cost tracking, or set up gross-margin observability, follow these instructions.
|
|
4
|
+
|
|
5
|
+
Tollgate wraps your LLM provider client and tracks every API call — attributing cost to a customer and run, then joining it with revenue to show real-time gross margin per customer.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
Add to environment:
|
|
10
|
+
```
|
|
11
|
+
TOLLGATE_API_KEY=tg_live_your_key_here
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Install:
|
|
15
|
+
```bash
|
|
16
|
+
npm install @tollgateai/sdk # TypeScript
|
|
17
|
+
pip install tollgateai # Python
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Wrap the provider (TypeScript)
|
|
21
|
+
|
|
22
|
+
Anthropic:
|
|
23
|
+
```typescript
|
|
24
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
25
|
+
import { createTollgateClient, wrapAnthropic } from '@tollgateai/sdk';
|
|
26
|
+
|
|
27
|
+
const tollgate = createTollgateClient();
|
|
28
|
+
const anthropic = wrapAnthropic(new Anthropic(), tollgate, {
|
|
29
|
+
customerId: 'cust_acme',
|
|
30
|
+
runId: 'ticket_8842',
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
OpenAI:
|
|
35
|
+
```typescript
|
|
36
|
+
import OpenAI from 'openai';
|
|
37
|
+
import { createTollgateClient, wrapOpenAI } from '@tollgateai/sdk';
|
|
38
|
+
|
|
39
|
+
const tollgate = createTollgateClient();
|
|
40
|
+
const openai = wrapOpenAI(new OpenAI(), tollgate, { customerId: 'cust_acme', runId: 'ticket_8842' });
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
OpenAI-compatible (Groq, OpenRouter, vLLM, Together):
|
|
44
|
+
```typescript
|
|
45
|
+
const client = wrapOpenAI(
|
|
46
|
+
new OpenAI({ baseURL: 'https://api.groq.com/openai/v1', apiKey: process.env.GROQ_API_KEY }),
|
|
47
|
+
tollgate,
|
|
48
|
+
{ customerId: 'cust_acme', runId: 'ticket_8842', provider: 'openai_compatible' },
|
|
49
|
+
);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Google Gemini:
|
|
53
|
+
```typescript
|
|
54
|
+
import { GoogleGenAI } from '@google/genai';
|
|
55
|
+
import { createTollgateClient, wrapGemini } from '@tollgateai/sdk';
|
|
56
|
+
const gemini = wrapGemini(new GoogleGenAI({ apiKey: process.env.GOOGLE_API_KEY }), tollgate, { customerId: 'cust_acme', runId: 'ticket_8842' });
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
AWS Bedrock:
|
|
60
|
+
```typescript
|
|
61
|
+
import { BedrockRuntimeClient } from '@aws-sdk/client-bedrock-runtime';
|
|
62
|
+
import { createTollgateClient, wrapBedrock } from '@tollgateai/sdk';
|
|
63
|
+
const bedrock = wrapBedrock(new BedrockRuntimeClient({ region: 'us-east-1' }), tollgate, { customerId: 'cust_acme', runId: 'ticket_8842' });
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Wrap the provider (Python)
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from tollgate import create_tollgate_client, wrap_anthropic, wrap_openai
|
|
70
|
+
|
|
71
|
+
tollgate = create_tollgate_client()
|
|
72
|
+
|
|
73
|
+
import anthropic
|
|
74
|
+
client = wrap_anthropic(anthropic.Anthropic(), tollgate, customer_id="cust_acme", run_id="ticket_8842")
|
|
75
|
+
|
|
76
|
+
from openai import OpenAI
|
|
77
|
+
client = wrap_openai(OpenAI(), tollgate, customer_id="cust_acme", run_id="ticket_8842")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## OpenAI streaming
|
|
81
|
+
|
|
82
|
+
Add stream_options or token counts won't be captured:
|
|
83
|
+
```typescript
|
|
84
|
+
const stream = await openai.chat.completions.create({
|
|
85
|
+
model: 'gpt-4o',
|
|
86
|
+
messages: [...],
|
|
87
|
+
stream: true,
|
|
88
|
+
stream_options: { include_usage: true },
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Close a multi-step run
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
await tollgate.resolve({
|
|
96
|
+
runId: 'ticket_8842',
|
|
97
|
+
customerId: 'cust_acme',
|
|
98
|
+
outcome: 'resolved', // 'resolved' | 'escalated' | 'failed'
|
|
99
|
+
revenueUnitCents: 50,
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Rules
|
|
104
|
+
|
|
105
|
+
- idempotencyKey must be unique per event — use runId#step_N pattern
|
|
106
|
+
- Never send prompt content (messages, content, input, output fields are rejected)
|
|
107
|
+
- reasoningTokens bills at output rate — pass separately
|
|
108
|
+
- cachedTokens bills at reduced rate — pass separately
|
|
109
|
+
- outcome only on the closing event of a run
|
|
110
|
+
|
|
111
|
+
## Direct REST API
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
POST https://tollgateai.dev/api/track
|
|
115
|
+
Authorization: Bearer tg_live_…
|
|
116
|
+
Content-Type: application/json
|
|
117
|
+
|
|
118
|
+
{ "customerId": "cust_acme", "runId": "ticket_8842", "provider": "anthropic",
|
|
119
|
+
"model": "claude-opus-4-8", "tokensIn": 1200, "tokensOut": 340,
|
|
120
|
+
"idempotencyKey": "ticket_8842#step_1" }
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Full docs: https://www.tollgateai.dev/docs
|