tokenmeter 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +220 -6
- package/dist/config.d.ts +7 -84
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -158
- package/dist/config.js.map +1 -1
- package/dist/exporter/PostgresExporter.d.ts.map +1 -1
- package/dist/exporter/PostgresExporter.js +4 -2
- package/dist/exporter/PostgresExporter.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/instrumentation/proxy.d.ts.map +1 -1
- package/dist/instrumentation/proxy.js +279 -51
- package/dist/instrumentation/proxy.js.map +1 -1
- package/dist/instrumentation/strategies/index.d.ts +33 -0
- package/dist/instrumentation/strategies/index.d.ts.map +1 -1
- package/dist/instrumentation/strategies/index.js +146 -17
- package/dist/instrumentation/strategies/index.js.map +1 -1
- package/dist/integrations/inngest/index.d.ts +0 -38
- package/dist/integrations/inngest/index.d.ts.map +1 -1
- package/dist/integrations/inngest/index.js +0 -49
- package/dist/integrations/inngest/index.js.map +1 -1
- package/dist/integrations/vercel-ai/index.d.ts.map +1 -1
- package/dist/integrations/vercel-ai/index.js +2 -1
- package/dist/integrations/vercel-ai/index.js.map +1 -1
- package/dist/pricing/manifest.bundled.d.ts +24 -0
- package/dist/pricing/manifest.bundled.d.ts.map +1 -0
- package/dist/pricing/manifest.bundled.js +13347 -0
- package/dist/pricing/manifest.bundled.js.map +1 -0
- package/dist/pricing/manifest.d.ts +24 -10
- package/dist/pricing/manifest.d.ts.map +1 -1
- package/dist/pricing/manifest.js +189 -118
- package/dist/pricing/manifest.js.map +1 -1
- package/dist/pricing/schema.d.ts +22 -13
- package/dist/pricing/schema.d.ts.map +1 -1
- package/dist/pricing/schema.js +5 -2
- package/dist/pricing/schema.js.map +1 -1
- package/dist/processor/TokenMeterProcessor.d.ts +37 -7
- package/dist/processor/TokenMeterProcessor.d.ts.map +1 -1
- package/dist/processor/TokenMeterProcessor.js +45 -24
- package/dist/processor/TokenMeterProcessor.js.map +1 -1
- package/dist/query/client.d.ts +0 -24
- package/dist/query/client.d.ts.map +1 -1
- package/dist/query/client.js +42 -9
- package/dist/query/client.js.map +1 -1
- package/dist/registry.d.ts +127 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +144 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +61 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +3 -2
|
@@ -1,8 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TokenMeter Span Processor
|
|
3
3
|
*
|
|
4
|
-
* An OpenTelemetry SpanProcessor that calculates
|
|
5
|
-
*
|
|
4
|
+
* An OpenTelemetry SpanProcessor that calculates and logs costs for spans
|
|
5
|
+
* with usage data. Useful for debugging and observability of AI costs.
|
|
6
|
+
*
|
|
7
|
+
* ## Important Limitation
|
|
8
|
+
*
|
|
9
|
+
* This processor CANNOT add cost attributes to spans after they end.
|
|
10
|
+
* OpenTelemetry's `ReadableSpan` interface doesn't allow attribute modification
|
|
11
|
+
* after `span.end()` is called. The processor can only:
|
|
12
|
+
* - Log calculated costs for debugging
|
|
13
|
+
* - Validate pricing configuration
|
|
14
|
+
* - Use with `pricingOverrides` for cost estimation
|
|
15
|
+
*
|
|
16
|
+
* ## When to Use
|
|
17
|
+
*
|
|
18
|
+
* 1. **Debugging**: Verify cost calculations during development
|
|
19
|
+
* 2. **Monitoring non-monitor() spans**: Log costs for spans from external
|
|
20
|
+
* sources (e.g., Vercel AI SDK's experimental_telemetry) that already
|
|
21
|
+
* include usage data
|
|
22
|
+
*
|
|
23
|
+
* ## For Production Cost Tracking
|
|
24
|
+
*
|
|
25
|
+
* Use `monitor()` to wrap your AI clients instead - it calculates and adds
|
|
26
|
+
* `tokenmeter.cost_usd` to spans BEFORE they end, which is the proper approach.
|
|
27
|
+
*
|
|
28
|
+
* @see monitor() for production cost tracking
|
|
6
29
|
*/
|
|
7
30
|
import type { Context } from "@opentelemetry/api";
|
|
8
31
|
import type { SpanProcessor, ReadableSpan, Span } from "@opentelemetry/sdk-trace-base";
|
|
@@ -10,13 +33,18 @@ import type { TokenMeterProcessorConfig } from "../types.js";
|
|
|
10
33
|
/**
|
|
11
34
|
* TokenMeter SpanProcessor
|
|
12
35
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
36
|
+
* Calculates costs for spans with usage data and logs them.
|
|
37
|
+
*
|
|
38
|
+
* **Note**: This processor cannot add cost attributes to spans after they end.
|
|
39
|
+
* For production cost tracking, use `monitor()` instead.
|
|
15
40
|
*
|
|
16
41
|
* @example
|
|
17
42
|
* ```typescript
|
|
18
43
|
* import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
19
|
-
* import { TokenMeterProcessor } from 'tokenmeter';
|
|
44
|
+
* import { TokenMeterProcessor, configureLogger } from 'tokenmeter';
|
|
45
|
+
*
|
|
46
|
+
* // Enable logging to see calculated costs
|
|
47
|
+
* configureLogger({ level: 'debug' });
|
|
20
48
|
*
|
|
21
49
|
* const provider = new NodeTracerProvider();
|
|
22
50
|
* provider.addSpanProcessor(new TokenMeterProcessor());
|
|
@@ -33,9 +61,11 @@ export declare class TokenMeterProcessor implements SpanProcessor {
|
|
|
33
61
|
/**
|
|
34
62
|
* Called when a span starts (no-op for TokenMeter)
|
|
35
63
|
*/
|
|
36
|
-
onStart(
|
|
64
|
+
onStart(_span: Span, _parentContext: Context): void;
|
|
37
65
|
/**
|
|
38
|
-
* Called when a span ends -
|
|
66
|
+
* Called when a span ends - calculates and logs cost.
|
|
67
|
+
*
|
|
68
|
+
* Note: Cannot add cost attribute to span after end() - use monitor() for that.
|
|
39
69
|
*/
|
|
40
70
|
onEnd(span: ReadableSpan): void;
|
|
41
71
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenMeterProcessor.d.ts","sourceRoot":"","sources":["../../src/processor/TokenMeterProcessor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"TokenMeterProcessor.d.ts","sourceRoot":"","sources":["../../src/processor/TokenMeterProcessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,IAAI,EACL,MAAM,+BAA+B,CAAC;AAOvC,OAAO,KAAK,EAAE,yBAAyB,EAAmB,MAAM,aAAa,CAAC;AAI9E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,mBAAoB,YAAW,aAAa;IACvD,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,gBAAgB,CAA+B;gBAE3C,MAAM,GAAE,yBAA8B;YAQpC,iBAAiB;IAU/B;;OAEG;IACH,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,GAAG,IAAI;IAInD;;;;OAIG;IACH,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAuC/B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA0BzB;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAO/B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAGlC;AAED,eAAe,mBAAmB,CAAC"}
|
|
@@ -1,21 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TokenMeter Span Processor
|
|
3
3
|
*
|
|
4
|
-
* An OpenTelemetry SpanProcessor that calculates
|
|
5
|
-
*
|
|
4
|
+
* An OpenTelemetry SpanProcessor that calculates and logs costs for spans
|
|
5
|
+
* with usage data. Useful for debugging and observability of AI costs.
|
|
6
|
+
*
|
|
7
|
+
* ## Important Limitation
|
|
8
|
+
*
|
|
9
|
+
* This processor CANNOT add cost attributes to spans after they end.
|
|
10
|
+
* OpenTelemetry's `ReadableSpan` interface doesn't allow attribute modification
|
|
11
|
+
* after `span.end()` is called. The processor can only:
|
|
12
|
+
* - Log calculated costs for debugging
|
|
13
|
+
* - Validate pricing configuration
|
|
14
|
+
* - Use with `pricingOverrides` for cost estimation
|
|
15
|
+
*
|
|
16
|
+
* ## When to Use
|
|
17
|
+
*
|
|
18
|
+
* 1. **Debugging**: Verify cost calculations during development
|
|
19
|
+
* 2. **Monitoring non-monitor() spans**: Log costs for spans from external
|
|
20
|
+
* sources (e.g., Vercel AI SDK's experimental_telemetry) that already
|
|
21
|
+
* include usage data
|
|
22
|
+
*
|
|
23
|
+
* ## For Production Cost Tracking
|
|
24
|
+
*
|
|
25
|
+
* Use `monitor()` to wrap your AI clients instead - it calculates and adds
|
|
26
|
+
* `tokenmeter.cost_usd` to spans BEFORE they end, which is the proper approach.
|
|
27
|
+
*
|
|
28
|
+
* @see monitor() for production cost tracking
|
|
6
29
|
*/
|
|
7
30
|
import { loadManifest, getModelPricing, calculateCost, getCachedManifest, } from "../pricing/manifest.js";
|
|
8
31
|
import { TM_ATTRIBUTES, GEN_AI_ATTRIBUTES } from "../types.js";
|
|
32
|
+
import { logger } from "../logger.js";
|
|
9
33
|
/**
|
|
10
34
|
* TokenMeter SpanProcessor
|
|
11
35
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
36
|
+
* Calculates costs for spans with usage data and logs them.
|
|
37
|
+
*
|
|
38
|
+
* **Note**: This processor cannot add cost attributes to spans after they end.
|
|
39
|
+
* For production cost tracking, use `monitor()` instead.
|
|
14
40
|
*
|
|
15
41
|
* @example
|
|
16
42
|
* ```typescript
|
|
17
43
|
* import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
18
|
-
* import { TokenMeterProcessor } from 'tokenmeter';
|
|
44
|
+
* import { TokenMeterProcessor, configureLogger } from 'tokenmeter';
|
|
45
|
+
*
|
|
46
|
+
* // Enable logging to see calculated costs
|
|
47
|
+
* configureLogger({ level: 'debug' });
|
|
19
48
|
*
|
|
20
49
|
* const provider = new NodeTracerProvider();
|
|
21
50
|
* provider.addSpanProcessor(new TokenMeterProcessor());
|
|
@@ -40,21 +69,21 @@ export class TokenMeterProcessor {
|
|
|
40
69
|
});
|
|
41
70
|
}
|
|
42
71
|
catch (error) {
|
|
43
|
-
|
|
72
|
+
logger.error("Failed to load pricing manifest:", error);
|
|
44
73
|
}
|
|
45
74
|
}
|
|
46
75
|
/**
|
|
47
76
|
* Called when a span starts (no-op for TokenMeter)
|
|
48
77
|
*/
|
|
49
|
-
onStart(
|
|
50
|
-
//
|
|
51
|
-
// Cost calculation happens on end
|
|
78
|
+
onStart(_span, _parentContext) {
|
|
79
|
+
// No-op: Cost calculation happens in onEnd
|
|
52
80
|
}
|
|
53
81
|
/**
|
|
54
|
-
* Called when a span ends -
|
|
82
|
+
* Called when a span ends - calculates and logs cost.
|
|
83
|
+
*
|
|
84
|
+
* Note: Cannot add cost attribute to span after end() - use monitor() for that.
|
|
55
85
|
*/
|
|
56
86
|
onEnd(span) {
|
|
57
|
-
// Get usage attributes from span
|
|
58
87
|
const attrs = span.attributes;
|
|
59
88
|
// Check if this span has usage data
|
|
60
89
|
const inputUnits = attrs[TM_ATTRIBUTES.INPUT_UNITS] ||
|
|
@@ -77,18 +106,10 @@ export class TokenMeterProcessor {
|
|
|
77
106
|
inputUnits,
|
|
78
107
|
outputUnits,
|
|
79
108
|
});
|
|
80
|
-
//
|
|
81
|
-
// Note:
|
|
82
|
-
// We use a workaround by storing cost in the span's resource or via events
|
|
83
|
-
// For now, we'll set it directly (works with most exporters)
|
|
109
|
+
// Log calculated cost for debugging
|
|
110
|
+
// Note: We cannot add this to the span - ReadableSpan is immutable after end()
|
|
84
111
|
if (cost !== null) {
|
|
85
|
-
|
|
86
|
-
// We need to use a custom approach - storing in a side channel or using events
|
|
87
|
-
// For this implementation, we'll log a warning and add via span events
|
|
88
|
-
// A production implementation would use a custom exporter or modify before end
|
|
89
|
-
// The proper way is to calculate cost BEFORE span.end() in the proxy
|
|
90
|
-
// This processor is for catching spans from other sources (like Vercel AI SDK)
|
|
91
|
-
console.debug(`[tokenmeter] Calculated cost for ${provider}/${model}: $${cost.toFixed(6)}`);
|
|
112
|
+
logger.debug(`Calculated cost for ${provider}/${model}: $${cost.toFixed(6)}`);
|
|
92
113
|
}
|
|
93
114
|
}
|
|
94
115
|
/**
|
|
@@ -102,12 +123,12 @@ export class TokenMeterProcessor {
|
|
|
102
123
|
// Fall back to manifest
|
|
103
124
|
const manifest = this.manifest || getCachedManifest();
|
|
104
125
|
if (!manifest) {
|
|
105
|
-
|
|
126
|
+
logger.warn("Pricing manifest not loaded, cannot calculate cost");
|
|
106
127
|
return null;
|
|
107
128
|
}
|
|
108
129
|
const pricing = getModelPricing(provider, model, manifest);
|
|
109
130
|
if (!pricing) {
|
|
110
|
-
|
|
131
|
+
logger.warn(`No pricing found for ${provider}/${model}`);
|
|
111
132
|
return 0; // Return 0, not null, to indicate we tried but found no pricing
|
|
112
133
|
}
|
|
113
134
|
return calculateCost(usage, pricing);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenMeterProcessor.js","sourceRoot":"","sources":["../../src/processor/TokenMeterProcessor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"TokenMeterProcessor.js","sourceRoot":"","sources":["../../src/processor/TokenMeterProcessor.ts"],"names":[],"mappings":"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"}
|
package/dist/query/client.d.ts
CHANGED
|
@@ -32,30 +32,6 @@ export interface QueryClient {
|
|
|
32
32
|
/** Close the database connection */
|
|
33
33
|
close(): Promise<void>;
|
|
34
34
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Create a query client for cost aggregation
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* ```typescript
|
|
40
|
-
* import { createQueryClient } from 'tokenmeter/query';
|
|
41
|
-
*
|
|
42
|
-
* const client = createQueryClient({
|
|
43
|
-
* connectionString: process.env.DATABASE_URL,
|
|
44
|
-
* });
|
|
45
|
-
*
|
|
46
|
-
* // Get total costs for an organization
|
|
47
|
-
* const orgCosts = await client.getCostByOrg('org_123', {
|
|
48
|
-
* from: '2024-01-01',
|
|
49
|
-
* to: '2024-01-31',
|
|
50
|
-
* groupBy: ['model'],
|
|
51
|
-
* });
|
|
52
|
-
*
|
|
53
|
-
* console.log(`Total: $${orgCosts.totalCost}`);
|
|
54
|
-
* for (const group of orgCosts.groups ?? []) {
|
|
55
|
-
* console.log(` ${group.key.model}: $${group.cost}`);
|
|
56
|
-
* }
|
|
57
|
-
* ```
|
|
58
|
-
*/
|
|
59
35
|
export declare function createQueryClient(config: QueryClientConfig): Promise<QueryClient>;
|
|
60
36
|
export default createQueryClient;
|
|
61
37
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/query/client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAUhE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,mCAAmC;IACnC,gBAAgB,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,qDAAqD;IACrD,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1D,oCAAoC;IACpC,aAAa,CACX,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,GACzC,OAAO,CAAC,UAAU,CAAC,CAAC;IACvB,4CAA4C;IAC5C,YAAY,CACV,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,GACjD,OAAO,CAAC,UAAU,CAAC,CAAC;IACvB,qCAAqC;IACrC,cAAc,CACZ,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC,GACxC,OAAO,CAAC,UAAU,CAAC,CAAC;IACvB,wCAAwC;IACxC,iBAAiB,CACf,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,GAC3C,OAAO,CAAC,UAAU,CAAC,CAAC;IACvB,0CAA0C;IAC1C,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACzD,oCAAoC;IACpC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/query/client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAUhE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,mCAAmC;IACnC,gBAAgB,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,qDAAqD;IACrD,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1D,oCAAoC;IACpC,aAAa,CACX,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,GACzC,OAAO,CAAC,UAAU,CAAC,CAAC;IACvB,4CAA4C;IAC5C,YAAY,CACV,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,GACjD,OAAO,CAAC,UAAU,CAAC,CAAC;IACvB,qCAAqC;IACrC,cAAc,CACZ,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC,GACxC,OAAO,CAAC,UAAU,CAAC,CAAC;IACvB,wCAAwC;IACxC,iBAAiB,CACf,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,GAC3C,OAAO,CAAC,UAAU,CAAC,CAAC;IACvB,0CAA0C;IAC1C,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACzD,oCAAoC;IACpC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AA4CD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,WAAW,CAAC,CA+PtB;AAED,eAAe,iBAAiB,CAAC"}
|
package/dist/query/client.js
CHANGED
|
@@ -27,13 +27,36 @@
|
|
|
27
27
|
* }
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
|
+
/**
|
|
31
|
+
* Validate table name to prevent SQL injection
|
|
32
|
+
*/
|
|
33
|
+
function validateTableName(name) {
|
|
34
|
+
// Only allow alphanumeric characters and underscores
|
|
35
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
36
|
+
throw new Error(`Invalid table name: "${name}". Table names must start with a letter or underscore and contain only alphanumeric characters and underscores.`);
|
|
37
|
+
}
|
|
38
|
+
// Limit length to prevent abuse
|
|
39
|
+
if (name.length > 63) {
|
|
40
|
+
throw new Error(`Table name too long: "${name}". Maximum length is 63 characters.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
30
43
|
export async function createQueryClient(config) {
|
|
31
44
|
const tableName = config.tableName ?? "tokenmeter_events";
|
|
45
|
+
// Validate table name to prevent SQL injection
|
|
46
|
+
validateTableName(tableName);
|
|
32
47
|
// Dynamic import to avoid requiring pg at load time
|
|
33
48
|
const { Pool } = await import("pg");
|
|
34
49
|
const pool = new Pool({
|
|
35
50
|
connectionString: config.connectionString,
|
|
36
51
|
});
|
|
52
|
+
// Test connection on initialization
|
|
53
|
+
try {
|
|
54
|
+
await pool.query("SELECT 1");
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
await pool.end();
|
|
58
|
+
throw new Error(`Failed to connect to database: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
59
|
+
}
|
|
37
60
|
/**
|
|
38
61
|
* Build WHERE clause from options
|
|
39
62
|
*/
|
|
@@ -75,17 +98,27 @@ export async function createQueryClient(config) {
|
|
|
75
98
|
return { clause, values, nextIndex: paramIndex };
|
|
76
99
|
}
|
|
77
100
|
/**
|
|
78
|
-
*
|
|
101
|
+
* Allowed groupBy fields (whitelist for SQL injection prevention)
|
|
102
|
+
*/
|
|
103
|
+
const ALLOWED_GROUP_BY_FIELDS = {
|
|
104
|
+
provider: "provider",
|
|
105
|
+
model: "model",
|
|
106
|
+
organizationId: "organization_id",
|
|
107
|
+
userId: "user_id",
|
|
108
|
+
workflowId: "workflow_id",
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Map groupBy field names to column names.
|
|
112
|
+
* Throws an error for unknown fields to prevent SQL injection.
|
|
113
|
+
*
|
|
114
|
+
* @throws {Error} If field is not in the allowed whitelist
|
|
79
115
|
*/
|
|
80
116
|
function mapGroupByField(field) {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
workflowId: "workflow_id",
|
|
87
|
-
};
|
|
88
|
-
return fieldMap[field] ?? field;
|
|
117
|
+
const column = ALLOWED_GROUP_BY_FIELDS[field];
|
|
118
|
+
if (!column) {
|
|
119
|
+
throw new Error(`Invalid groupBy field: "${field}". Allowed fields: ${Object.keys(ALLOWED_GROUP_BY_FIELDS).join(", ")}`);
|
|
120
|
+
}
|
|
121
|
+
return column;
|
|
89
122
|
}
|
|
90
123
|
/**
|
|
91
124
|
* Execute a cost query
|
package/dist/query/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/query/client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAsDH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAyB;IAEzB,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,mBAAmB,CAAC;IAE1D,oDAAoD;IACpD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,IAAI,GAAS,IAAI,IAAI,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;KAC1C,CAAC,CAAC;IAEH;;OAEG;IACH,SAAS,gBAAgB,CACvB,OAAyB,EACzB,kBAA0B,CAAC;QAE3B,MAAM,UAAU,GAAa,EAAE,CAAC;QAChC,MAAM,MAAM,GAAc,EAAE,CAAC;QAC7B,IAAI,UAAU,GAAG,eAAe,CAAC;QAEjC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,UAAU,CAAC,IAAI,CAAC,kBAAkB,UAAU,EAAE,CAAC,CAAC;YAChD,MAAM,CAAC,IAAI,CACT,OAAO,CAAC,IAAI,YAAY,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CACrE,CAAC;YACF,UAAU,EAAE,CAAC;QACf,CAAC;QAED,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;YACf,UAAU,CAAC,IAAI,CAAC,kBAAkB,UAAU,EAAE,CAAC,CAAC;YAChD,MAAM,CAAC,IAAI,CACT,OAAO,CAAC,EAAE,YAAY,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAC/D,CAAC;YACF,UAAU,EAAE,CAAC;QACf,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,UAAU,CAAC,IAAI,CAAC,eAAe,UAAU,EAAE,CAAC,CAAC;YAC7C,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAC9B,UAAU,EAAE,CAAC;QACf,CAAC;QAED,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,UAAU,CAAC,IAAI,CAAC,YAAY,UAAU,EAAE,CAAC,CAAC;YAC1C,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC3B,UAAU,EAAE,CAAC;QACf,CAAC;QAED,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;YAC3B,UAAU,CAAC,IAAI,CAAC,sBAAsB,UAAU,EAAE,CAAC,CAAC;YACpD,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;YACpC,UAAU,EAAE,CAAC;QACf,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,UAAU,CAAC,IAAI,CAAC,cAAc,UAAU,EAAE,CAAC,CAAC;YAC5C,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC5B,UAAU,EAAE,CAAC;QACf,CAAC;QAED,MAAM,MAAM,GACV,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAEnE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;IACnD,CAAC;IAED;;OAEG;IACH,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/query/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider Registry
|
|
3
|
+
*
|
|
4
|
+
* Extensible system for registering custom AI providers.
|
|
5
|
+
* Allows users to add support for new providers without modifying core code.
|
|
6
|
+
*/
|
|
7
|
+
import type { ExtractionStrategy, UsageData } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Symbol used to mark objects with their provider.
|
|
10
|
+
* Allows explicit provider identification without duck-typing.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { TOKENMETER_PROVIDER } from 'tokenmeter';
|
|
15
|
+
*
|
|
16
|
+
* const client = new CustomAIClient();
|
|
17
|
+
* client[TOKENMETER_PROVIDER] = 'custom-ai';
|
|
18
|
+
*
|
|
19
|
+
* const monitored = monitor(client); // Will use 'custom-ai' as provider
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare const TOKENMETER_PROVIDER: unique symbol;
|
|
23
|
+
/**
|
|
24
|
+
* Configuration for registering a custom provider
|
|
25
|
+
*/
|
|
26
|
+
export interface ProviderConfig {
|
|
27
|
+
/** Unique provider identifier (e.g., 'google-ai', 'custom-llm') */
|
|
28
|
+
name: string;
|
|
29
|
+
/**
|
|
30
|
+
* Function to detect if a client belongs to this provider.
|
|
31
|
+
* Called during monitor() if no explicit provider is set.
|
|
32
|
+
*
|
|
33
|
+
* @param client - The client instance being monitored
|
|
34
|
+
* @returns true if this provider should handle the client
|
|
35
|
+
*/
|
|
36
|
+
detect?: (client: unknown) => boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Function to extract usage data from API responses.
|
|
39
|
+
*
|
|
40
|
+
* @param response - The API response
|
|
41
|
+
* @param args - The original arguments passed to the API call
|
|
42
|
+
* @returns Usage data or null if extraction failed
|
|
43
|
+
*/
|
|
44
|
+
extractUsage?: (response: unknown, args: unknown[]) => Partial<UsageData> | null;
|
|
45
|
+
/**
|
|
46
|
+
* Function to extract the model name from request args or response.
|
|
47
|
+
*
|
|
48
|
+
* @param args - The original arguments passed to the API call
|
|
49
|
+
* @param response - The API response (if available)
|
|
50
|
+
* @returns Model identifier string
|
|
51
|
+
*/
|
|
52
|
+
extractModel?: (args: unknown[], response?: unknown) => string;
|
|
53
|
+
/**
|
|
54
|
+
* List of method names that are factory methods returning objects to be proxied.
|
|
55
|
+
* These methods won't create spans but their return values will be wrapped.
|
|
56
|
+
*
|
|
57
|
+
* @example ['getGenerativeModel', 'createClient', 'getModel']
|
|
58
|
+
*/
|
|
59
|
+
factoryMethods?: string[];
|
|
60
|
+
/**
|
|
61
|
+
* Full extraction strategy (alternative to extractUsage/extractModel).
|
|
62
|
+
* If provided, this takes precedence over extractUsage and extractModel.
|
|
63
|
+
*/
|
|
64
|
+
strategy?: ExtractionStrategy;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Register a custom provider with tokenmeter.
|
|
68
|
+
*
|
|
69
|
+
* This is the recommended way to add support for new AI providers
|
|
70
|
+
* without modifying the core library code.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* import { registerProvider } from 'tokenmeter';
|
|
75
|
+
*
|
|
76
|
+
* registerProvider({
|
|
77
|
+
* name: 'my-ai-provider',
|
|
78
|
+
* detect: (client) => 'generateText' in client && 'myProvider' in client,
|
|
79
|
+
* extractUsage: (response) => ({
|
|
80
|
+
* inputUnits: response.usage?.inputTokens,
|
|
81
|
+
* outputUnits: response.usage?.outputTokens,
|
|
82
|
+
* }),
|
|
83
|
+
* extractModel: (args) => args[0]?.model || 'unknown',
|
|
84
|
+
* factoryMethods: ['createModel'],
|
|
85
|
+
* });
|
|
86
|
+
*
|
|
87
|
+
* // Now you can monitor your custom client
|
|
88
|
+
* const client = monitor(new MyAIProvider());
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export declare function registerProvider(config: ProviderConfig): void;
|
|
92
|
+
/**
|
|
93
|
+
* Unregister a provider
|
|
94
|
+
*/
|
|
95
|
+
export declare function unregisterProvider(name: string): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Get a registered provider by name
|
|
98
|
+
*/
|
|
99
|
+
export declare function getProvider(name: string): ProviderConfig | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Get all registered providers
|
|
102
|
+
*/
|
|
103
|
+
export declare function getRegisteredProviders(): ProviderConfig[];
|
|
104
|
+
/**
|
|
105
|
+
* Clear all registered providers (useful for testing)
|
|
106
|
+
*/
|
|
107
|
+
export declare function clearProviderRegistry(): void;
|
|
108
|
+
/**
|
|
109
|
+
* Detect provider from a client using the registry.
|
|
110
|
+
* Returns the first matching provider or undefined.
|
|
111
|
+
*
|
|
112
|
+
* @internal
|
|
113
|
+
*/
|
|
114
|
+
export declare function detectProviderFromRegistry(client: unknown): string | undefined;
|
|
115
|
+
/**
|
|
116
|
+
* Get factory methods for a provider
|
|
117
|
+
*
|
|
118
|
+
* @internal
|
|
119
|
+
*/
|
|
120
|
+
export declare function getFactoryMethods(provider: string): string[];
|
|
121
|
+
/**
|
|
122
|
+
* Get extraction strategy for a provider from the registry
|
|
123
|
+
*
|
|
124
|
+
* @internal
|
|
125
|
+
*/
|
|
126
|
+
export declare function getRegisteredStrategy(provider: string): ExtractionStrategy | undefined;
|
|
127
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEhE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,eAAoC,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAEtC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,CACb,QAAQ,EAAE,OAAO,EACjB,IAAI,EAAE,OAAO,EAAE,KACZ,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;IAE/B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,MAAM,CAAC;IAE/D;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAE1B;;;OAGG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B;AAOD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAoC7D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAEpE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,cAAc,EAAE,CAEzD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAkB9E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAG5D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GACf,kBAAkB,GAAG,SAAS,CAGhC"}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider Registry
|
|
3
|
+
*
|
|
4
|
+
* Extensible system for registering custom AI providers.
|
|
5
|
+
* Allows users to add support for new providers without modifying core code.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Symbol used to mark objects with their provider.
|
|
9
|
+
* Allows explicit provider identification without duck-typing.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { TOKENMETER_PROVIDER } from 'tokenmeter';
|
|
14
|
+
*
|
|
15
|
+
* const client = new CustomAIClient();
|
|
16
|
+
* client[TOKENMETER_PROVIDER] = 'custom-ai';
|
|
17
|
+
*
|
|
18
|
+
* const monitored = monitor(client); // Will use 'custom-ai' as provider
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export const TOKENMETER_PROVIDER = Symbol.for("tokenmeter.provider");
|
|
22
|
+
/**
|
|
23
|
+
* Internal registry storage
|
|
24
|
+
*/
|
|
25
|
+
const providerRegistry = new Map();
|
|
26
|
+
/**
|
|
27
|
+
* Register a custom provider with tokenmeter.
|
|
28
|
+
*
|
|
29
|
+
* This is the recommended way to add support for new AI providers
|
|
30
|
+
* without modifying the core library code.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* import { registerProvider } from 'tokenmeter';
|
|
35
|
+
*
|
|
36
|
+
* registerProvider({
|
|
37
|
+
* name: 'my-ai-provider',
|
|
38
|
+
* detect: (client) => 'generateText' in client && 'myProvider' in client,
|
|
39
|
+
* extractUsage: (response) => ({
|
|
40
|
+
* inputUnits: response.usage?.inputTokens,
|
|
41
|
+
* outputUnits: response.usage?.outputTokens,
|
|
42
|
+
* }),
|
|
43
|
+
* extractModel: (args) => args[0]?.model || 'unknown',
|
|
44
|
+
* factoryMethods: ['createModel'],
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* // Now you can monitor your custom client
|
|
48
|
+
* const client = monitor(new MyAIProvider());
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function registerProvider(config) {
|
|
52
|
+
if (!config.name) {
|
|
53
|
+
throw new Error("Provider name is required");
|
|
54
|
+
}
|
|
55
|
+
// If strategy is not provided, create one from extractUsage/extractModel
|
|
56
|
+
if (!config.strategy && config.extractUsage) {
|
|
57
|
+
config.strategy = {
|
|
58
|
+
provider: config.name,
|
|
59
|
+
canHandle: (_methodPath, result) => {
|
|
60
|
+
// Try to extract usage - if it succeeds, we can handle this result
|
|
61
|
+
const usage = config.extractUsage(result, []);
|
|
62
|
+
return usage !== null;
|
|
63
|
+
},
|
|
64
|
+
extract: (_methodPath, result, args) => {
|
|
65
|
+
const partialUsage = config.extractUsage(result, args);
|
|
66
|
+
if (!partialUsage)
|
|
67
|
+
return null;
|
|
68
|
+
const model = config.extractModel
|
|
69
|
+
? config.extractModel(args, result)
|
|
70
|
+
: "unknown";
|
|
71
|
+
return {
|
|
72
|
+
provider: config.name,
|
|
73
|
+
model,
|
|
74
|
+
...partialUsage,
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
providerRegistry.set(config.name, config);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Unregister a provider
|
|
83
|
+
*/
|
|
84
|
+
export function unregisterProvider(name) {
|
|
85
|
+
return providerRegistry.delete(name);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get a registered provider by name
|
|
89
|
+
*/
|
|
90
|
+
export function getProvider(name) {
|
|
91
|
+
return providerRegistry.get(name);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get all registered providers
|
|
95
|
+
*/
|
|
96
|
+
export function getRegisteredProviders() {
|
|
97
|
+
return Array.from(providerRegistry.values());
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Clear all registered providers (useful for testing)
|
|
101
|
+
*/
|
|
102
|
+
export function clearProviderRegistry() {
|
|
103
|
+
providerRegistry.clear();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Detect provider from a client using the registry.
|
|
107
|
+
* Returns the first matching provider or undefined.
|
|
108
|
+
*
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
export function detectProviderFromRegistry(client) {
|
|
112
|
+
// First, check for explicit Symbol-based provider
|
|
113
|
+
if (client &&
|
|
114
|
+
typeof client === "object" &&
|
|
115
|
+
TOKENMETER_PROVIDER in client) {
|
|
116
|
+
return client[TOKENMETER_PROVIDER];
|
|
117
|
+
}
|
|
118
|
+
// Then check registered providers
|
|
119
|
+
for (const [name, config] of providerRegistry) {
|
|
120
|
+
if (config.detect && config.detect(client)) {
|
|
121
|
+
return name;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get factory methods for a provider
|
|
128
|
+
*
|
|
129
|
+
* @internal
|
|
130
|
+
*/
|
|
131
|
+
export function getFactoryMethods(provider) {
|
|
132
|
+
const config = providerRegistry.get(provider);
|
|
133
|
+
return config?.factoryMethods || [];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get extraction strategy for a provider from the registry
|
|
137
|
+
*
|
|
138
|
+
* @internal
|
|
139
|
+
*/
|
|
140
|
+
export function getRegisteredStrategy(provider) {
|
|
141
|
+
const config = providerRegistry.get(provider);
|
|
142
|
+
return config?.strategy;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|