tokenmeter 0.9.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/LICENSE +21 -0
- package/README.md +346 -0
- package/dist/__tests__/context.test.d.ts +2 -0
- package/dist/__tests__/context.test.d.ts.map +1 -0
- package/dist/__tests__/context.test.js +94 -0
- package/dist/__tests__/context.test.js.map +1 -0
- package/dist/__tests__/elevenlabs.test.d.ts +2 -0
- package/dist/__tests__/elevenlabs.test.d.ts.map +1 -0
- package/dist/__tests__/elevenlabs.test.js +108 -0
- package/dist/__tests__/elevenlabs.test.js.map +1 -0
- package/dist/__tests__/fal.test.d.ts +2 -0
- package/dist/__tests__/fal.test.d.ts.map +1 -0
- package/dist/__tests__/fal.test.js +153 -0
- package/dist/__tests__/fal.test.js.map +1 -0
- package/dist/__tests__/pricing.test.d.ts +2 -0
- package/dist/__tests__/pricing.test.d.ts.map +1 -0
- package/dist/__tests__/pricing.test.js +76 -0
- package/dist/__tests__/pricing.test.js.map +1 -0
- package/dist/__tests__/recorder.test.d.ts +2 -0
- package/dist/__tests__/recorder.test.d.ts.map +1 -0
- package/dist/__tests__/recorder.test.js +133 -0
- package/dist/__tests__/recorder.test.js.map +1 -0
- package/dist/__tests__/storage.test.d.ts +2 -0
- package/dist/__tests__/storage.test.d.ts.map +1 -0
- package/dist/__tests__/storage.test.js +106 -0
- package/dist/__tests__/storage.test.js.map +1 -0
- package/dist/client/index.d.ts +8 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +7 -0
- package/dist/client/index.js.map +1 -0
- package/dist/config.d.ts +92 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +166 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +80 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +131 -0
- package/dist/context.js.map +1 -0
- package/dist/exporter/PostgresExporter.d.ts +82 -0
- package/dist/exporter/PostgresExporter.d.ts.map +1 -0
- package/dist/exporter/PostgresExporter.js +237 -0
- package/dist/exporter/PostgresExporter.js.map +1 -0
- package/dist/exporter/index.d.ts +8 -0
- package/dist/exporter/index.d.ts.map +1 -0
- package/dist/exporter/index.js +7 -0
- package/dist/exporter/index.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation/proxy.d.ts +26 -0
- package/dist/instrumentation/proxy.d.ts.map +1 -0
- package/dist/instrumentation/proxy.js +337 -0
- package/dist/instrumentation/proxy.js.map +1 -0
- package/dist/instrumentation/strategies/index.d.ts +55 -0
- package/dist/instrumentation/strategies/index.d.ts.map +1 -0
- package/dist/instrumentation/strategies/index.js +429 -0
- package/dist/instrumentation/strategies/index.js.map +1 -0
- package/dist/integrations/express/index.d.ts +137 -0
- package/dist/integrations/express/index.d.ts.map +1 -0
- package/dist/integrations/express/index.js +186 -0
- package/dist/integrations/express/index.js.map +1 -0
- package/dist/integrations/inngest/index.d.ts +222 -0
- package/dist/integrations/inngest/index.d.ts.map +1 -0
- package/dist/integrations/inngest/index.js +223 -0
- package/dist/integrations/inngest/index.js.map +1 -0
- package/dist/integrations/langfuse/index.d.ts +170 -0
- package/dist/integrations/langfuse/index.d.ts.map +1 -0
- package/dist/integrations/langfuse/index.js +225 -0
- package/dist/integrations/langfuse/index.js.map +1 -0
- package/dist/integrations/next/index.d.ts +138 -0
- package/dist/integrations/next/index.d.ts.map +1 -0
- package/dist/integrations/next/index.js +170 -0
- package/dist/integrations/next/index.js.map +1 -0
- package/dist/integrations/nextjs/index.d.ts +198 -0
- package/dist/integrations/nextjs/index.d.ts.map +1 -0
- package/dist/integrations/nextjs/index.js +181 -0
- package/dist/integrations/nextjs/index.js.map +1 -0
- package/dist/integrations/vercel-ai/index.d.ts +288 -0
- package/dist/integrations/vercel-ai/index.d.ts.map +1 -0
- package/dist/integrations/vercel-ai/index.js +260 -0
- package/dist/integrations/vercel-ai/index.js.map +1 -0
- package/dist/logger.d.ts +58 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +89 -0
- package/dist/logger.js.map +1 -0
- package/dist/pricing/catalog.d.ts +10 -0
- package/dist/pricing/catalog.d.ts.map +1 -0
- package/dist/pricing/catalog.js +297 -0
- package/dist/pricing/catalog.js.map +1 -0
- package/dist/pricing/index.d.ts +77 -0
- package/dist/pricing/index.d.ts.map +1 -0
- package/dist/pricing/index.js +251 -0
- package/dist/pricing/index.js.map +1 -0
- package/dist/pricing/manifest.d.ts +156 -0
- package/dist/pricing/manifest.d.ts.map +1 -0
- package/dist/pricing/manifest.js +381 -0
- package/dist/pricing/manifest.js.map +1 -0
- package/dist/pricing/manifest.json +12786 -0
- package/dist/pricing/providers/anthropic.json +253 -0
- package/dist/pricing/providers/bedrock.json +341 -0
- package/dist/pricing/providers/bfl.json +220 -0
- package/dist/pricing/providers/elevenlabs.json +142 -0
- package/dist/pricing/providers/fal.json +15866 -0
- package/dist/pricing/providers/google.json +346 -0
- package/dist/pricing/providers/openai.json +1035 -0
- package/dist/pricing/schema.d.ts +102 -0
- package/dist/pricing/schema.d.ts.map +1 -0
- package/dist/pricing/schema.js +56 -0
- package/dist/pricing/schema.js.map +1 -0
- package/dist/processor/TokenMeterProcessor.d.ts +55 -0
- package/dist/processor/TokenMeterProcessor.d.ts.map +1 -0
- package/dist/processor/TokenMeterProcessor.js +132 -0
- package/dist/processor/TokenMeterProcessor.js.map +1 -0
- package/dist/query/client.d.ts +61 -0
- package/dist/query/client.d.ts.map +1 -0
- package/dist/query/client.js +206 -0
- package/dist/query/client.js.map +1 -0
- package/dist/query/index.d.ts +8 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/index.js +7 -0
- package/dist/query/index.js.map +1 -0
- package/dist/recorder.d.ts +74 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/recorder.js +227 -0
- package/dist/recorder.js.map +1 -0
- package/dist/sdks/anthropic.d.ts +21 -0
- package/dist/sdks/anthropic.d.ts.map +1 -0
- package/dist/sdks/anthropic.js +258 -0
- package/dist/sdks/anthropic.js.map +1 -0
- package/dist/sdks/elevenlabs.d.ts +59 -0
- package/dist/sdks/elevenlabs.d.ts.map +1 -0
- package/dist/sdks/elevenlabs.js +192 -0
- package/dist/sdks/elevenlabs.js.map +1 -0
- package/dist/sdks/fal.d.ts +102 -0
- package/dist/sdks/fal.d.ts.map +1 -0
- package/dist/sdks/fal.js +306 -0
- package/dist/sdks/fal.js.map +1 -0
- package/dist/sdks/openai.d.ts +17 -0
- package/dist/sdks/openai.d.ts.map +1 -0
- package/dist/sdks/openai.js +191 -0
- package/dist/sdks/openai.js.map +1 -0
- package/dist/storage/interface.d.ts +15 -0
- package/dist/storage/interface.d.ts.map +1 -0
- package/dist/storage/interface.js +53 -0
- package/dist/storage/interface.js.map +1 -0
- package/dist/storage/prisma.d.ts +15 -0
- package/dist/storage/prisma.d.ts.map +1 -0
- package/dist/storage/prisma.js +135 -0
- package/dist/storage/prisma.js.map +1 -0
- package/dist/types.d.ts +206 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +45 -0
- package/dist/types.js.map +1 -0
- package/dist/vercel-ai/index.d.ts +89 -0
- package/dist/vercel-ai/index.d.ts.map +1 -0
- package/dist/vercel-ai/index.js +298 -0
- package/dist/vercel-ai/index.js.map +1 -0
- package/package.json +119 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Lukas Minnebeck
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# tokenmeter
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/tokenmeter)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
OpenTelemetry-native cost tracking for AI workflows. Track real USD costs per user, workflow, and provider with zero code changes.
|
|
7
|
+
|
|
8
|
+
## Why tokenmeter?
|
|
9
|
+
|
|
10
|
+
AI costs are hard to track. Tokens flow through multiple providers, streaming responses don't report usage upfront, and attributing costs to users or workflows requires custom instrumentation everywhere.
|
|
11
|
+
|
|
12
|
+
tokenmeter solves this by:
|
|
13
|
+
|
|
14
|
+
- **Wrapping AI clients transparently** - `monitor(client)` returns the same type, no code changes needed
|
|
15
|
+
- **Calculating costs automatically** - Uses up-to-date pricing for OpenAI, Anthropic, Google, fal.ai, ElevenLabs
|
|
16
|
+
- **Propagating context** - `withAttributes()` attaches user/org/workflow IDs to all nested AI calls
|
|
17
|
+
- **Integrating with OTel** - Export to Datadog, Jaeger, Honeycomb, or persist to PostgreSQL
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install tokenmeter @opentelemetry/api @opentelemetry/sdk-trace-node
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import OpenAI from 'openai';
|
|
29
|
+
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
30
|
+
import { SimpleSpanProcessor, ConsoleSpanExporter } from '@opentelemetry/sdk-trace-base';
|
|
31
|
+
import { monitor, withAttributes, TokenMeterProcessor } from 'tokenmeter';
|
|
32
|
+
|
|
33
|
+
// 1. Set up OpenTelemetry with TokenMeter processor
|
|
34
|
+
const provider = new NodeTracerProvider();
|
|
35
|
+
provider.addSpanProcessor(new TokenMeterProcessor());
|
|
36
|
+
provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
|
|
37
|
+
provider.register();
|
|
38
|
+
|
|
39
|
+
// 2. Wrap your AI client
|
|
40
|
+
const openai = monitor(new OpenAI());
|
|
41
|
+
|
|
42
|
+
// 3. Track costs with context
|
|
43
|
+
await withAttributes({ 'user.id': 'user_123' }, async () => {
|
|
44
|
+
const response = await openai.chat.completions.create({
|
|
45
|
+
model: 'gpt-4o',
|
|
46
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
47
|
+
});
|
|
48
|
+
console.log(response.choices[0].message.content);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Spans now include: tokenmeter.cost_usd, tokenmeter.provider, tokenmeter.model, user.id
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Supported Providers
|
|
55
|
+
|
|
56
|
+
| Provider | Models | Pricing Unit |
|
|
57
|
+
|----------|--------|--------------|
|
|
58
|
+
| **OpenAI** | GPT-4o, GPT-4-turbo, o1, o3, GPT-3.5, embeddings, DALL-E, Whisper | per 1M tokens |
|
|
59
|
+
| **Anthropic** | Claude 4, Claude 3.5, Claude 3 | per 1M tokens |
|
|
60
|
+
| **Google** | Gemini 2.0, Gemini 1.5 | per 1M tokens |
|
|
61
|
+
| **fal.ai** | 900+ models (Flux, SDXL, Kling, Runway, etc.) | per request/megapixel/second |
|
|
62
|
+
| **ElevenLabs** | All TTS models | per 1K characters |
|
|
63
|
+
|
|
64
|
+
## Core Concepts
|
|
65
|
+
|
|
66
|
+
### `monitor(client)`
|
|
67
|
+
|
|
68
|
+
Wraps any supported AI client with a Proxy that intercepts API calls, extracts usage data, and creates OpenTelemetry spans with cost attributes.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import OpenAI from 'openai';
|
|
72
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
73
|
+
import { fal } from '@fal-ai/client';
|
|
74
|
+
import { monitor } from 'tokenmeter';
|
|
75
|
+
|
|
76
|
+
const openai = monitor(new OpenAI());
|
|
77
|
+
const anthropic = monitor(new Anthropic());
|
|
78
|
+
const trackedFal = monitor(fal);
|
|
79
|
+
|
|
80
|
+
// Types are fully preserved - no changes to your code
|
|
81
|
+
const response = await openai.chat.completions.create({...});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `withAttributes(attrs, fn)`
|
|
85
|
+
|
|
86
|
+
Sets context attributes inherited by all AI calls within the callback. Uses OpenTelemetry Baggage for propagation.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { withAttributes } from 'tokenmeter';
|
|
90
|
+
|
|
91
|
+
await withAttributes({ 'user.id': 'user_123', 'org.id': 'acme' }, async () => {
|
|
92
|
+
// All AI calls here are tagged with user.id and org.id
|
|
93
|
+
await openai.chat.completions.create({...});
|
|
94
|
+
await anthropic.messages.create({...});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Nesting merges attributes
|
|
98
|
+
await withAttributes({ 'org.id': 'acme' }, async () => {
|
|
99
|
+
await withAttributes({ 'user.id': 'user_123' }, async () => {
|
|
100
|
+
// Has both org.id and user.id
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### `TokenMeterProcessor`
|
|
106
|
+
|
|
107
|
+
An OpenTelemetry SpanProcessor that calculates costs from span attributes and adds `tokenmeter.cost_usd`.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
111
|
+
import { TokenMeterProcessor } from 'tokenmeter';
|
|
112
|
+
|
|
113
|
+
const provider = new NodeTracerProvider();
|
|
114
|
+
provider.addSpanProcessor(new TokenMeterProcessor());
|
|
115
|
+
provider.register();
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Span Attributes
|
|
119
|
+
|
|
120
|
+
tokenmeter adds these attributes to spans:
|
|
121
|
+
|
|
122
|
+
| Attribute | Type | Description |
|
|
123
|
+
|-----------|------|-------------|
|
|
124
|
+
| `tokenmeter.cost_usd` | number | Calculated cost in USD |
|
|
125
|
+
| `tokenmeter.provider` | string | Provider name |
|
|
126
|
+
| `tokenmeter.model` | string | Model identifier |
|
|
127
|
+
| `gen_ai.usage.input_tokens` | number | Input token count |
|
|
128
|
+
| `gen_ai.usage.output_tokens` | number | Output token count |
|
|
129
|
+
|
|
130
|
+
Plus any attributes set via `withAttributes()` (e.g., `user.id`, `org.id`, `workflow.id`).
|
|
131
|
+
|
|
132
|
+
## Framework Integrations
|
|
133
|
+
|
|
134
|
+
### Next.js App Router
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { withTokenmeter } from 'tokenmeter/next';
|
|
138
|
+
|
|
139
|
+
async function handler(request: Request) {
|
|
140
|
+
const response = await openai.chat.completions.create({...});
|
|
141
|
+
return Response.json({ message: response.choices[0].message.content });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export const POST = withTokenmeter(handler, (request) => ({
|
|
145
|
+
userId: request.headers.get('x-user-id') || undefined,
|
|
146
|
+
}));
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Inngest
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { withInngest, getInngestTraceHeaders } from 'tokenmeter/inngest';
|
|
153
|
+
|
|
154
|
+
// Send events with trace context
|
|
155
|
+
await inngest.send({
|
|
156
|
+
name: 'ai/generate',
|
|
157
|
+
data: { prompt: '...' },
|
|
158
|
+
...getInngestTraceHeaders(),
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Restore context in function
|
|
162
|
+
export const generateFn = inngest.createFunction(
|
|
163
|
+
{ id: 'generate' },
|
|
164
|
+
{ event: 'ai/generate' },
|
|
165
|
+
withInngest(async ({ event }) => {
|
|
166
|
+
await openai.chat.completions.create({...}); // Linked to original trace
|
|
167
|
+
})
|
|
168
|
+
);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## PostgreSQL Persistence
|
|
172
|
+
|
|
173
|
+
Store costs for querying and billing.
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
177
|
+
import { PostgresExporter } from 'tokenmeter/exporter';
|
|
178
|
+
import { createQueryClient } from 'tokenmeter/client';
|
|
179
|
+
|
|
180
|
+
// Export spans to PostgreSQL
|
|
181
|
+
provider.addSpanProcessor(new BatchSpanProcessor(
|
|
182
|
+
new PostgresExporter({ connectionString: process.env.DATABASE_URL })
|
|
183
|
+
));
|
|
184
|
+
|
|
185
|
+
// Query costs
|
|
186
|
+
const client = createQueryClient({ connectionString: process.env.DATABASE_URL });
|
|
187
|
+
|
|
188
|
+
const { totalCost } = await client.getCostByUser('user_123', {
|
|
189
|
+
from: new Date('2024-01-01'),
|
|
190
|
+
to: new Date('2024-01-31'),
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const byModel = await client.getCosts({ groupBy: ['model'] });
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
See [DATABASE_SETUP.md](./DATABASE_SETUP.md) for schema and setup instructions.
|
|
197
|
+
|
|
198
|
+
## Streaming Support
|
|
199
|
+
|
|
200
|
+
tokenmeter handles streaming responses automatically.
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
// OpenAI - requires stream_options for usage
|
|
204
|
+
const stream = await openai.chat.completions.create({
|
|
205
|
+
model: 'gpt-4o',
|
|
206
|
+
messages: [{...}],
|
|
207
|
+
stream: true,
|
|
208
|
+
stream_options: { include_usage: true },
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
for await (const chunk of stream) {
|
|
212
|
+
process.stdout.write(chunk.choices[0]?.delta?.content || '');
|
|
213
|
+
}
|
|
214
|
+
// Cost calculated when stream completes
|
|
215
|
+
|
|
216
|
+
// Anthropic streaming works out of the box
|
|
217
|
+
const stream = anthropic.messages.stream({...});
|
|
218
|
+
for await (const event of stream) {...}
|
|
219
|
+
const finalMessage = await stream.finalMessage();
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Cross-Service Propagation
|
|
223
|
+
|
|
224
|
+
For distributed systems, propagate trace context across service boundaries.
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { extractTraceHeaders, withExtractedContext } from 'tokenmeter';
|
|
228
|
+
|
|
229
|
+
// Service A: Extract headers
|
|
230
|
+
const headers = extractTraceHeaders();
|
|
231
|
+
await fetch('https://service-b.example.com', { headers });
|
|
232
|
+
|
|
233
|
+
// Service B: Restore context
|
|
234
|
+
await withExtractedContext(req.headers, async () => {
|
|
235
|
+
await openai.chat.completions.create({...}); // Part of Service A's trace
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Pricing Configuration
|
|
240
|
+
|
|
241
|
+
tokenmeter fetches pricing from a remote manifest with local fallback.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { configurePricing, loadManifest } from 'tokenmeter';
|
|
245
|
+
|
|
246
|
+
// Use offline mode (bundled pricing only)
|
|
247
|
+
configurePricing({ offlineMode: true });
|
|
248
|
+
|
|
249
|
+
// Custom pricing API
|
|
250
|
+
configurePricing({ apiUrl: 'https://your-api.com/pricing' });
|
|
251
|
+
|
|
252
|
+
// Force refresh
|
|
253
|
+
await loadManifest({ forceRefresh: true });
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Export Destinations
|
|
257
|
+
|
|
258
|
+
### Datadog
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
262
|
+
|
|
263
|
+
provider.addSpanProcessor(new BatchSpanProcessor(
|
|
264
|
+
new OTLPTraceExporter({
|
|
265
|
+
url: 'https://trace.agent.datadoghq.com/v0.4/traces',
|
|
266
|
+
headers: { 'DD-API-KEY': process.env.DD_API_KEY },
|
|
267
|
+
})
|
|
268
|
+
));
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Honeycomb
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
provider.addSpanProcessor(new BatchSpanProcessor(
|
|
275
|
+
new OTLPTraceExporter({
|
|
276
|
+
url: 'https://api.honeycomb.io/v1/traces',
|
|
277
|
+
headers: { 'x-honeycomb-team': process.env.HONEYCOMB_API_KEY },
|
|
278
|
+
})
|
|
279
|
+
));
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Jaeger
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
provider.addSpanProcessor(new BatchSpanProcessor(
|
|
286
|
+
new OTLPTraceExporter({ url: 'http://localhost:4318/v1/traces' })
|
|
287
|
+
));
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## API Reference
|
|
291
|
+
|
|
292
|
+
### Core
|
|
293
|
+
|
|
294
|
+
| Export | Description |
|
|
295
|
+
|--------|-------------|
|
|
296
|
+
| `monitor(client, options?)` | Wrap AI client with cost tracking |
|
|
297
|
+
| `withAttributes(attrs, fn)` | Set context attributes for nested calls |
|
|
298
|
+
| `extractTraceHeaders()` | Get W3C trace headers for propagation |
|
|
299
|
+
| `withExtractedContext(headers, fn)` | Restore context from headers |
|
|
300
|
+
|
|
301
|
+
### Processor & Exporter
|
|
302
|
+
|
|
303
|
+
| Export | Description |
|
|
304
|
+
|--------|-------------|
|
|
305
|
+
| `TokenMeterProcessor` | OTel SpanProcessor for cost calculation |
|
|
306
|
+
| `PostgresExporter` | OTel SpanExporter for PostgreSQL |
|
|
307
|
+
|
|
308
|
+
### Query Client (`tokenmeter/client`)
|
|
309
|
+
|
|
310
|
+
| Method | Description |
|
|
311
|
+
|--------|-------------|
|
|
312
|
+
| `createQueryClient(config)` | Create query client |
|
|
313
|
+
| `client.getCosts(options)` | Query with filters and grouping |
|
|
314
|
+
| `client.getCostByUser(userId, options?)` | Get user costs |
|
|
315
|
+
| `client.getCostByOrg(orgId, options?)` | Get organization costs |
|
|
316
|
+
| `client.getWorkflowCost(workflowId)` | Get workflow costs |
|
|
317
|
+
|
|
318
|
+
### Integrations
|
|
319
|
+
|
|
320
|
+
| Export | Description |
|
|
321
|
+
|--------|-------------|
|
|
322
|
+
| `withTokenmeter` (`tokenmeter/next`) | Next.js App Router wrapper |
|
|
323
|
+
| `withInngest` (`tokenmeter/inngest`) | Inngest function wrapper |
|
|
324
|
+
| `getInngestTraceHeaders` (`tokenmeter/inngest`) | Get headers for Inngest events |
|
|
325
|
+
|
|
326
|
+
## Contributing
|
|
327
|
+
|
|
328
|
+
Contributions are welcome! Please read our [Contributing Guide](./CONTRIBUTING.md) for details.
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# Install dependencies
|
|
332
|
+
pnpm install
|
|
333
|
+
|
|
334
|
+
# Run tests
|
|
335
|
+
pnpm test
|
|
336
|
+
|
|
337
|
+
# Build
|
|
338
|
+
pnpm build
|
|
339
|
+
|
|
340
|
+
# Type check
|
|
341
|
+
pnpm check-types
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## License
|
|
345
|
+
|
|
346
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/context.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { withCostTrace, getCurrentContext, getCurrentCost, addCostToContext, getContextEvents, addEventToContext, } from "../context.js";
|
|
3
|
+
describe("Context Module", () => {
|
|
4
|
+
describe("withCostTrace", () => {
|
|
5
|
+
it("should create a trace context", async () => {
|
|
6
|
+
let capturedContext;
|
|
7
|
+
await withCostTrace({ identifier: "user_123", workflowType: "test" }, async () => {
|
|
8
|
+
capturedContext = getCurrentContext();
|
|
9
|
+
});
|
|
10
|
+
expect(capturedContext).toBeDefined();
|
|
11
|
+
expect(capturedContext?.identifier).toBe("user_123");
|
|
12
|
+
expect(capturedContext?.workflowType).toBe("test");
|
|
13
|
+
});
|
|
14
|
+
it("should generate workflowId if not provided", async () => {
|
|
15
|
+
let capturedWorkflowId;
|
|
16
|
+
await withCostTrace({ identifier: "user_123" }, async () => {
|
|
17
|
+
capturedWorkflowId = getCurrentContext()?.workflowId;
|
|
18
|
+
});
|
|
19
|
+
expect(capturedWorkflowId).toBeDefined();
|
|
20
|
+
expect(capturedWorkflowId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
|
|
21
|
+
});
|
|
22
|
+
it("should use provided workflowId", async () => {
|
|
23
|
+
await withCostTrace({ identifier: "user_123", workflowId: "custom_wf_123" }, async () => {
|
|
24
|
+
expect(getCurrentContext()?.workflowId).toBe("custom_wf_123");
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
it("should return the result of the callback", async () => {
|
|
28
|
+
const result = await withCostTrace({ identifier: "user_123" }, async () => {
|
|
29
|
+
return "test_result";
|
|
30
|
+
});
|
|
31
|
+
expect(result).toBe("test_result");
|
|
32
|
+
});
|
|
33
|
+
it("should clear context after callback completes", async () => {
|
|
34
|
+
await withCostTrace({ identifier: "user_123" }, async () => {
|
|
35
|
+
expect(getCurrentContext()).toBeDefined();
|
|
36
|
+
});
|
|
37
|
+
expect(getCurrentContext()).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
it("should handle nested traces (inner overrides outer)", async () => {
|
|
40
|
+
await withCostTrace({ identifier: "outer_user" }, async () => {
|
|
41
|
+
expect(getCurrentContext()?.identifier).toBe("outer_user");
|
|
42
|
+
await withCostTrace({ identifier: "inner_user" }, async () => {
|
|
43
|
+
expect(getCurrentContext()?.identifier).toBe("inner_user");
|
|
44
|
+
});
|
|
45
|
+
expect(getCurrentContext()?.identifier).toBe("outer_user");
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe("getCurrentCost", () => {
|
|
50
|
+
it("should return 0 when no context", () => {
|
|
51
|
+
expect(getCurrentCost()).toBe(0);
|
|
52
|
+
});
|
|
53
|
+
it("should return accumulated cost within context", async () => {
|
|
54
|
+
await withCostTrace({ identifier: "user_123" }, async () => {
|
|
55
|
+
expect(getCurrentCost()).toBe(0);
|
|
56
|
+
addCostToContext(0.5);
|
|
57
|
+
expect(getCurrentCost()).toBe(0.5);
|
|
58
|
+
addCostToContext(0.3);
|
|
59
|
+
expect(getCurrentCost()).toBe(0.8);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("getContextEvents", () => {
|
|
64
|
+
it("should return empty array when no context", () => {
|
|
65
|
+
expect(getContextEvents()).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
it("should track events within context", async () => {
|
|
68
|
+
await withCostTrace({ identifier: "user_123" }, async () => {
|
|
69
|
+
const event = {
|
|
70
|
+
identifier: "user_123",
|
|
71
|
+
provider: "openai",
|
|
72
|
+
model: "gpt-4o",
|
|
73
|
+
costUsd: 0.01,
|
|
74
|
+
};
|
|
75
|
+
addEventToContext(event);
|
|
76
|
+
const events = getContextEvents();
|
|
77
|
+
expect(events).toHaveLength(1);
|
|
78
|
+
expect(events[0]?.provider).toBe("openai");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
it("should clear events after context ends", async () => {
|
|
82
|
+
await withCostTrace({ identifier: "user_123" }, async () => {
|
|
83
|
+
addEventToContext({
|
|
84
|
+
identifier: "user_123",
|
|
85
|
+
provider: "openai",
|
|
86
|
+
model: "gpt-4o",
|
|
87
|
+
costUsd: 0.01,
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
expect(getContextEvents()).toEqual([]);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
//# sourceMappingURL=context.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.test.js","sourceRoot":"","sources":["../../src/__tests__/context.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elevenlabs.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/elevenlabs.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { trackElevenLabs } from "../sdks/elevenlabs.js";
|
|
3
|
+
import { init, shutdown, withCostTrace, flush } from "../index.js";
|
|
4
|
+
import { InMemoryStorageAdapter } from "../storage/interface.js";
|
|
5
|
+
describe("ElevenLabs SDK Wrapper", () => {
|
|
6
|
+
let storage;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
storage = new InMemoryStorageAdapter();
|
|
9
|
+
init({ storage });
|
|
10
|
+
});
|
|
11
|
+
afterEach(async () => {
|
|
12
|
+
await flush();
|
|
13
|
+
await shutdown();
|
|
14
|
+
});
|
|
15
|
+
const createMockClient = () => ({
|
|
16
|
+
textToSpeech: {
|
|
17
|
+
convert: vi.fn().mockResolvedValue(new ArrayBuffer(1024)),
|
|
18
|
+
convertAsStream: vi.fn().mockResolvedValue(new ReadableStream()),
|
|
19
|
+
},
|
|
20
|
+
generate: vi.fn().mockResolvedValue(new ArrayBuffer(1024)),
|
|
21
|
+
});
|
|
22
|
+
describe("trackElevenLabs", () => {
|
|
23
|
+
it("should wrap textToSpeech.convert and track costs", async () => {
|
|
24
|
+
const mockClient = createMockClient();
|
|
25
|
+
const trackedClient = trackElevenLabs(mockClient);
|
|
26
|
+
await withCostTrace({ identifier: "user_123" }, async () => {
|
|
27
|
+
await trackedClient.textToSpeech.convert("voice_123", {
|
|
28
|
+
text: "Hello, this is a test message with some text.",
|
|
29
|
+
model_id: "eleven_multilingual_v2",
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
await flush();
|
|
33
|
+
const events = storage.getEvents();
|
|
34
|
+
expect(events).toHaveLength(1);
|
|
35
|
+
expect(events[0]?.provider).toBe("elevenlabs");
|
|
36
|
+
expect(events[0]?.model).toBe("eleven_multilingual_v2");
|
|
37
|
+
expect(events[0]?.operation).toBe("text-to-speech");
|
|
38
|
+
expect(events[0]?.costUsd).toBeGreaterThan(0);
|
|
39
|
+
expect((events[0]?.metadata).characterCount).toBe(46);
|
|
40
|
+
});
|
|
41
|
+
it("should calculate cost based on character count", async () => {
|
|
42
|
+
const mockClient = createMockClient();
|
|
43
|
+
const trackedClient = trackElevenLabs(mockClient);
|
|
44
|
+
await withCostTrace({ identifier: "user_123" }, async () => {
|
|
45
|
+
// 1000 characters with eleven_multilingual_v2 ($0.30 per 1K chars)
|
|
46
|
+
await trackedClient.textToSpeech.convert("voice_123", {
|
|
47
|
+
text: "x".repeat(1000),
|
|
48
|
+
model_id: "eleven_multilingual_v2",
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
await flush();
|
|
52
|
+
const events = storage.getEvents();
|
|
53
|
+
expect(events[0]?.costUsd).toBeCloseTo(0.30, 4);
|
|
54
|
+
});
|
|
55
|
+
it("should use cheaper pricing for turbo model", async () => {
|
|
56
|
+
const mockClient = createMockClient();
|
|
57
|
+
const trackedClient = trackElevenLabs(mockClient);
|
|
58
|
+
await withCostTrace({ identifier: "user_123" }, async () => {
|
|
59
|
+
// 1000 characters with eleven_turbo_v2_5 ($0.15 per 1K chars)
|
|
60
|
+
await trackedClient.textToSpeech.convert("voice_123", {
|
|
61
|
+
text: "x".repeat(1000),
|
|
62
|
+
model_id: "eleven_turbo_v2_5",
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
await flush();
|
|
66
|
+
const events = storage.getEvents();
|
|
67
|
+
expect(events[0]?.costUsd).toBeCloseTo(0.15, 4);
|
|
68
|
+
});
|
|
69
|
+
it("should track failed calls", async () => {
|
|
70
|
+
const mockClient = createMockClient();
|
|
71
|
+
mockClient.textToSpeech.convert.mockRejectedValue(new Error("API Error"));
|
|
72
|
+
const trackedClient = trackElevenLabs(mockClient);
|
|
73
|
+
await withCostTrace({ identifier: "user_123" }, async () => {
|
|
74
|
+
try {
|
|
75
|
+
await trackedClient.textToSpeech.convert("voice_123", {
|
|
76
|
+
text: "Test",
|
|
77
|
+
model_id: "eleven_multilingual_v2",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
// Expected error
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
await flush();
|
|
85
|
+
const events = storage.getEvents();
|
|
86
|
+
expect(events).toHaveLength(1);
|
|
87
|
+
expect(events[0]?.status).toBe("failed");
|
|
88
|
+
expect(events[0]?.billable).toBe(false);
|
|
89
|
+
expect(events[0]?.errorMessage).toBe("API Error");
|
|
90
|
+
});
|
|
91
|
+
it("should wrap generate method", async () => {
|
|
92
|
+
const mockClient = createMockClient();
|
|
93
|
+
const trackedClient = trackElevenLabs(mockClient);
|
|
94
|
+
await withCostTrace({ identifier: "user_123" }, async () => {
|
|
95
|
+
await trackedClient.generate({
|
|
96
|
+
text: "Hello world",
|
|
97
|
+
voice: "voice_123",
|
|
98
|
+
model_id: "eleven_turbo_v2",
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
await flush();
|
|
102
|
+
const events = storage.getEvents();
|
|
103
|
+
expect(events).toHaveLength(1);
|
|
104
|
+
expect(events[0]?.provider).toBe("elevenlabs");
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
//# sourceMappingURL=elevenlabs.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elevenlabs.test.js","sourceRoot":"","sources":["../../src/__tests__/elevenlabs.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fal.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/fal.test.ts"],"names":[],"mappings":""}
|