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.
Files changed (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +346 -0
  3. package/dist/__tests__/context.test.d.ts +2 -0
  4. package/dist/__tests__/context.test.d.ts.map +1 -0
  5. package/dist/__tests__/context.test.js +94 -0
  6. package/dist/__tests__/context.test.js.map +1 -0
  7. package/dist/__tests__/elevenlabs.test.d.ts +2 -0
  8. package/dist/__tests__/elevenlabs.test.d.ts.map +1 -0
  9. package/dist/__tests__/elevenlabs.test.js +108 -0
  10. package/dist/__tests__/elevenlabs.test.js.map +1 -0
  11. package/dist/__tests__/fal.test.d.ts +2 -0
  12. package/dist/__tests__/fal.test.d.ts.map +1 -0
  13. package/dist/__tests__/fal.test.js +153 -0
  14. package/dist/__tests__/fal.test.js.map +1 -0
  15. package/dist/__tests__/pricing.test.d.ts +2 -0
  16. package/dist/__tests__/pricing.test.d.ts.map +1 -0
  17. package/dist/__tests__/pricing.test.js +76 -0
  18. package/dist/__tests__/pricing.test.js.map +1 -0
  19. package/dist/__tests__/recorder.test.d.ts +2 -0
  20. package/dist/__tests__/recorder.test.d.ts.map +1 -0
  21. package/dist/__tests__/recorder.test.js +133 -0
  22. package/dist/__tests__/recorder.test.js.map +1 -0
  23. package/dist/__tests__/storage.test.d.ts +2 -0
  24. package/dist/__tests__/storage.test.d.ts.map +1 -0
  25. package/dist/__tests__/storage.test.js +106 -0
  26. package/dist/__tests__/storage.test.js.map +1 -0
  27. package/dist/client/index.d.ts +8 -0
  28. package/dist/client/index.d.ts.map +1 -0
  29. package/dist/client/index.js +7 -0
  30. package/dist/client/index.js.map +1 -0
  31. package/dist/config.d.ts +92 -0
  32. package/dist/config.d.ts.map +1 -0
  33. package/dist/config.js +166 -0
  34. package/dist/config.js.map +1 -0
  35. package/dist/context.d.ts +80 -0
  36. package/dist/context.d.ts.map +1 -0
  37. package/dist/context.js +131 -0
  38. package/dist/context.js.map +1 -0
  39. package/dist/exporter/PostgresExporter.d.ts +82 -0
  40. package/dist/exporter/PostgresExporter.d.ts.map +1 -0
  41. package/dist/exporter/PostgresExporter.js +237 -0
  42. package/dist/exporter/PostgresExporter.js.map +1 -0
  43. package/dist/exporter/index.d.ts +8 -0
  44. package/dist/exporter/index.d.ts.map +1 -0
  45. package/dist/exporter/index.js +7 -0
  46. package/dist/exporter/index.js.map +1 -0
  47. package/dist/index.d.ts +31 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +37 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/instrumentation/proxy.d.ts +26 -0
  52. package/dist/instrumentation/proxy.d.ts.map +1 -0
  53. package/dist/instrumentation/proxy.js +337 -0
  54. package/dist/instrumentation/proxy.js.map +1 -0
  55. package/dist/instrumentation/strategies/index.d.ts +55 -0
  56. package/dist/instrumentation/strategies/index.d.ts.map +1 -0
  57. package/dist/instrumentation/strategies/index.js +429 -0
  58. package/dist/instrumentation/strategies/index.js.map +1 -0
  59. package/dist/integrations/express/index.d.ts +137 -0
  60. package/dist/integrations/express/index.d.ts.map +1 -0
  61. package/dist/integrations/express/index.js +186 -0
  62. package/dist/integrations/express/index.js.map +1 -0
  63. package/dist/integrations/inngest/index.d.ts +222 -0
  64. package/dist/integrations/inngest/index.d.ts.map +1 -0
  65. package/dist/integrations/inngest/index.js +223 -0
  66. package/dist/integrations/inngest/index.js.map +1 -0
  67. package/dist/integrations/langfuse/index.d.ts +170 -0
  68. package/dist/integrations/langfuse/index.d.ts.map +1 -0
  69. package/dist/integrations/langfuse/index.js +225 -0
  70. package/dist/integrations/langfuse/index.js.map +1 -0
  71. package/dist/integrations/next/index.d.ts +138 -0
  72. package/dist/integrations/next/index.d.ts.map +1 -0
  73. package/dist/integrations/next/index.js +170 -0
  74. package/dist/integrations/next/index.js.map +1 -0
  75. package/dist/integrations/nextjs/index.d.ts +198 -0
  76. package/dist/integrations/nextjs/index.d.ts.map +1 -0
  77. package/dist/integrations/nextjs/index.js +181 -0
  78. package/dist/integrations/nextjs/index.js.map +1 -0
  79. package/dist/integrations/vercel-ai/index.d.ts +288 -0
  80. package/dist/integrations/vercel-ai/index.d.ts.map +1 -0
  81. package/dist/integrations/vercel-ai/index.js +260 -0
  82. package/dist/integrations/vercel-ai/index.js.map +1 -0
  83. package/dist/logger.d.ts +58 -0
  84. package/dist/logger.d.ts.map +1 -0
  85. package/dist/logger.js +89 -0
  86. package/dist/logger.js.map +1 -0
  87. package/dist/pricing/catalog.d.ts +10 -0
  88. package/dist/pricing/catalog.d.ts.map +1 -0
  89. package/dist/pricing/catalog.js +297 -0
  90. package/dist/pricing/catalog.js.map +1 -0
  91. package/dist/pricing/index.d.ts +77 -0
  92. package/dist/pricing/index.d.ts.map +1 -0
  93. package/dist/pricing/index.js +251 -0
  94. package/dist/pricing/index.js.map +1 -0
  95. package/dist/pricing/manifest.d.ts +156 -0
  96. package/dist/pricing/manifest.d.ts.map +1 -0
  97. package/dist/pricing/manifest.js +381 -0
  98. package/dist/pricing/manifest.js.map +1 -0
  99. package/dist/pricing/manifest.json +12786 -0
  100. package/dist/pricing/providers/anthropic.json +253 -0
  101. package/dist/pricing/providers/bedrock.json +341 -0
  102. package/dist/pricing/providers/bfl.json +220 -0
  103. package/dist/pricing/providers/elevenlabs.json +142 -0
  104. package/dist/pricing/providers/fal.json +15866 -0
  105. package/dist/pricing/providers/google.json +346 -0
  106. package/dist/pricing/providers/openai.json +1035 -0
  107. package/dist/pricing/schema.d.ts +102 -0
  108. package/dist/pricing/schema.d.ts.map +1 -0
  109. package/dist/pricing/schema.js +56 -0
  110. package/dist/pricing/schema.js.map +1 -0
  111. package/dist/processor/TokenMeterProcessor.d.ts +55 -0
  112. package/dist/processor/TokenMeterProcessor.d.ts.map +1 -0
  113. package/dist/processor/TokenMeterProcessor.js +132 -0
  114. package/dist/processor/TokenMeterProcessor.js.map +1 -0
  115. package/dist/query/client.d.ts +61 -0
  116. package/dist/query/client.d.ts.map +1 -0
  117. package/dist/query/client.js +206 -0
  118. package/dist/query/client.js.map +1 -0
  119. package/dist/query/index.d.ts +8 -0
  120. package/dist/query/index.d.ts.map +1 -0
  121. package/dist/query/index.js +7 -0
  122. package/dist/query/index.js.map +1 -0
  123. package/dist/recorder.d.ts +74 -0
  124. package/dist/recorder.d.ts.map +1 -0
  125. package/dist/recorder.js +227 -0
  126. package/dist/recorder.js.map +1 -0
  127. package/dist/sdks/anthropic.d.ts +21 -0
  128. package/dist/sdks/anthropic.d.ts.map +1 -0
  129. package/dist/sdks/anthropic.js +258 -0
  130. package/dist/sdks/anthropic.js.map +1 -0
  131. package/dist/sdks/elevenlabs.d.ts +59 -0
  132. package/dist/sdks/elevenlabs.d.ts.map +1 -0
  133. package/dist/sdks/elevenlabs.js +192 -0
  134. package/dist/sdks/elevenlabs.js.map +1 -0
  135. package/dist/sdks/fal.d.ts +102 -0
  136. package/dist/sdks/fal.d.ts.map +1 -0
  137. package/dist/sdks/fal.js +306 -0
  138. package/dist/sdks/fal.js.map +1 -0
  139. package/dist/sdks/openai.d.ts +17 -0
  140. package/dist/sdks/openai.d.ts.map +1 -0
  141. package/dist/sdks/openai.js +191 -0
  142. package/dist/sdks/openai.js.map +1 -0
  143. package/dist/storage/interface.d.ts +15 -0
  144. package/dist/storage/interface.d.ts.map +1 -0
  145. package/dist/storage/interface.js +53 -0
  146. package/dist/storage/interface.js.map +1 -0
  147. package/dist/storage/prisma.d.ts +15 -0
  148. package/dist/storage/prisma.d.ts.map +1 -0
  149. package/dist/storage/prisma.js +135 -0
  150. package/dist/storage/prisma.js.map +1 -0
  151. package/dist/types.d.ts +206 -0
  152. package/dist/types.d.ts.map +1 -0
  153. package/dist/types.js +45 -0
  154. package/dist/types.js.map +1 -0
  155. package/dist/vercel-ai/index.d.ts +89 -0
  156. package/dist/vercel-ai/index.d.ts.map +1 -0
  157. package/dist/vercel-ai/index.js +298 -0
  158. package/dist/vercel-ai/index.js.map +1 -0
  159. 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
+ [![npm version](https://img.shields.io/npm/v/tokenmeter.svg)](https://www.npmjs.com/package/tokenmeter)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=context.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=elevenlabs.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fal.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fal.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/fal.test.ts"],"names":[],"mappings":""}