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
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pricing Schema Types
|
|
3
|
+
*
|
|
4
|
+
* Standardized types for provider pricing catalogs.
|
|
5
|
+
* These types define the structure of pricing JSON files.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Billing unit types supported across providers
|
|
9
|
+
*/
|
|
10
|
+
export type BillingUnit = "tokens" | "characters" | "images" | "megapixels" | "seconds" | "minutes" | "requests";
|
|
11
|
+
/**
|
|
12
|
+
* A single pricing entry with an effective date
|
|
13
|
+
* Prices are per unit (e.g., per 1K tokens, per image)
|
|
14
|
+
*/
|
|
15
|
+
export interface PricingEntry {
|
|
16
|
+
/** ISO 8601 date when this pricing became effective */
|
|
17
|
+
effectiveDate: string;
|
|
18
|
+
/** Cost per input unit (e.g., input tokens, characters) */
|
|
19
|
+
input?: number;
|
|
20
|
+
/** Cost per output unit (e.g., output tokens, generated images) */
|
|
21
|
+
output?: number;
|
|
22
|
+
/** Cost per cached input unit (prompt caching) */
|
|
23
|
+
cachedInput?: number;
|
|
24
|
+
/** Cost for cache creation/write (Anthropic) */
|
|
25
|
+
cacheWrite?: number;
|
|
26
|
+
/** Notes about this pricing change */
|
|
27
|
+
notes?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Model definition with pricing history
|
|
31
|
+
*/
|
|
32
|
+
export interface ModelPricing {
|
|
33
|
+
/** Display name for the model */
|
|
34
|
+
name?: string;
|
|
35
|
+
/** Billing unit for this model */
|
|
36
|
+
unit: BillingUnit;
|
|
37
|
+
/**
|
|
38
|
+
* Unit size - what the prices are per
|
|
39
|
+
* e.g., 1000 for "per 1K tokens", 1000000 for "per 1M tokens"
|
|
40
|
+
* Defaults to 1000 for tokens/characters, 1 for images/seconds
|
|
41
|
+
*/
|
|
42
|
+
unitSize?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Pricing history, sorted by effectiveDate ascending
|
|
45
|
+
* The last entry is the current pricing
|
|
46
|
+
*/
|
|
47
|
+
pricing: PricingEntry[];
|
|
48
|
+
/** Model aliases that should resolve to this model */
|
|
49
|
+
aliases?: string[];
|
|
50
|
+
/** Whether this model is deprecated */
|
|
51
|
+
deprecated?: boolean;
|
|
52
|
+
/** Deprecation date if applicable */
|
|
53
|
+
deprecatedDate?: string;
|
|
54
|
+
/** Replacement model if deprecated */
|
|
55
|
+
replacedBy?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Provider pricing catalog
|
|
59
|
+
*/
|
|
60
|
+
export interface ProviderPricing {
|
|
61
|
+
/** JSON schema reference */
|
|
62
|
+
$schema?: string;
|
|
63
|
+
/** Provider identifier */
|
|
64
|
+
provider: string;
|
|
65
|
+
/** Display name for the provider */
|
|
66
|
+
displayName: string;
|
|
67
|
+
/** URL to official pricing page */
|
|
68
|
+
source: string;
|
|
69
|
+
/** ISO 8601 date when this file was last updated */
|
|
70
|
+
lastUpdated: string;
|
|
71
|
+
/** Currency for all prices (default: USD) */
|
|
72
|
+
currency?: string;
|
|
73
|
+
/** Model pricing definitions, keyed by model ID */
|
|
74
|
+
models: Record<string, ModelPricing>;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Resolved pricing for a specific model at a point in time
|
|
78
|
+
*/
|
|
79
|
+
export interface ResolvedPricing {
|
|
80
|
+
provider: string;
|
|
81
|
+
model: string;
|
|
82
|
+
unit: BillingUnit;
|
|
83
|
+
unitSize: number;
|
|
84
|
+
input: number;
|
|
85
|
+
output: number;
|
|
86
|
+
cachedInput?: number;
|
|
87
|
+
cacheWrite?: number;
|
|
88
|
+
effectiveDate: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get the current pricing for a model (most recent effective date)
|
|
92
|
+
*/
|
|
93
|
+
export declare function getCurrentPricing(model: ModelPricing): PricingEntry | null;
|
|
94
|
+
/**
|
|
95
|
+
* Get pricing for a model at a specific date
|
|
96
|
+
*/
|
|
97
|
+
export declare function getPricingAtDate(model: ModelPricing, date: Date): PricingEntry | null;
|
|
98
|
+
/**
|
|
99
|
+
* Get the default unit size for a billing unit
|
|
100
|
+
*/
|
|
101
|
+
export declare function getDefaultUnitSize(unit: BillingUnit): number;
|
|
102
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/pricing/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,MAAM,WAAW,GACnB,QAAQ,GACR,YAAY,GACZ,QAAQ,GACR,YAAY,GACZ,SAAS,GACT,SAAS,GACT,UAAU,CAAC;AAEf;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,uDAAuD;IACvD,aAAa,EAAE,MAAM,CAAC;IAEtB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,iCAAiC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,kCAAkC;IAClC,IAAI,EAAE,WAAW,CAAC;IAElB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,OAAO,EAAE,YAAY,EAAE,CAAC;IAExB,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB,uCAAuC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,qCAAqC;IACrC,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,4BAA4B;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IAEjB,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IAEpB,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IAEf,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IAEpB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,YAAY,GAAG,IAAI,CAM1E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,IAAI,GACT,YAAY,GAAG,IAAI,CAoBrB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAc5D"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pricing Schema Types
|
|
3
|
+
*
|
|
4
|
+
* Standardized types for provider pricing catalogs.
|
|
5
|
+
* These types define the structure of pricing JSON files.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Get the current pricing for a model (most recent effective date)
|
|
9
|
+
*/
|
|
10
|
+
export function getCurrentPricing(model) {
|
|
11
|
+
if (!model.pricing || model.pricing.length === 0) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
// Return the last entry (most recent)
|
|
15
|
+
return model.pricing[model.pricing.length - 1] ?? null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get pricing for a model at a specific date
|
|
19
|
+
*/
|
|
20
|
+
export function getPricingAtDate(model, date) {
|
|
21
|
+
if (!model.pricing || model.pricing.length === 0) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const targetTime = date.getTime();
|
|
25
|
+
// Find the most recent pricing that was effective before or on the target date
|
|
26
|
+
let result = null;
|
|
27
|
+
for (const entry of model.pricing) {
|
|
28
|
+
const entryTime = new Date(entry.effectiveDate).getTime();
|
|
29
|
+
if (entryTime <= targetTime) {
|
|
30
|
+
result = entry;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
break; // Entries are sorted, no need to continue
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get the default unit size for a billing unit
|
|
40
|
+
*/
|
|
41
|
+
export function getDefaultUnitSize(unit) {
|
|
42
|
+
switch (unit) {
|
|
43
|
+
case "tokens":
|
|
44
|
+
case "characters":
|
|
45
|
+
return 1000; // Per 1K
|
|
46
|
+
case "images":
|
|
47
|
+
case "megapixels":
|
|
48
|
+
case "seconds":
|
|
49
|
+
case "minutes":
|
|
50
|
+
case "requests":
|
|
51
|
+
return 1;
|
|
52
|
+
default:
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/pricing/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAmHH;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAmB;IACnD,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,sCAAsC;IACtC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;AACzD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAmB,EACnB,IAAU;IAEV,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;IAElC,+EAA+E;IAC/E,IAAI,MAAM,GAAwB,IAAI,CAAC;IAEvC,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC;QAC1D,IAAI,SAAS,IAAI,UAAU,EAAE,CAAC;YAC5B,MAAM,GAAG,KAAK,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,0CAA0C;QACnD,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAiB;IAClD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,QAAQ,CAAC;QACd,KAAK,YAAY;YACf,OAAO,IAAI,CAAC,CAAC,SAAS;QACxB,KAAK,QAAQ,CAAC;QACd,KAAK,YAAY,CAAC;QAClB,KAAK,SAAS,CAAC;QACf,KAAK,SAAS,CAAC;QACf,KAAK,UAAU;YACb,OAAO,CAAC,CAAC;QACX;YACE,OAAO,CAAC,CAAC;IACb,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TokenMeter Span Processor
|
|
3
|
+
*
|
|
4
|
+
* An OpenTelemetry SpanProcessor that calculates costs based on usage data
|
|
5
|
+
* and adds cost attributes to spans before they are exported.
|
|
6
|
+
*/
|
|
7
|
+
import type { Context } from "@opentelemetry/api";
|
|
8
|
+
import type { SpanProcessor, ReadableSpan, Span } from "@opentelemetry/sdk-trace-base";
|
|
9
|
+
import type { TokenMeterProcessorConfig } from "../types.js";
|
|
10
|
+
/**
|
|
11
|
+
* TokenMeter SpanProcessor
|
|
12
|
+
*
|
|
13
|
+
* Intercepts spans on end and calculates costs based on usage attributes.
|
|
14
|
+
* The calculated cost is added as an attribute before the span is exported.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
19
|
+
* import { TokenMeterProcessor } from 'tokenmeter';
|
|
20
|
+
*
|
|
21
|
+
* const provider = new NodeTracerProvider();
|
|
22
|
+
* provider.addSpanProcessor(new TokenMeterProcessor());
|
|
23
|
+
* provider.register();
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare class TokenMeterProcessor implements SpanProcessor {
|
|
27
|
+
private manifest;
|
|
28
|
+
private manifestPromise;
|
|
29
|
+
private config;
|
|
30
|
+
private pricingOverrides;
|
|
31
|
+
constructor(config?: TokenMeterProcessorConfig);
|
|
32
|
+
private loadManifestAsync;
|
|
33
|
+
/**
|
|
34
|
+
* Called when a span starts (no-op for TokenMeter)
|
|
35
|
+
*/
|
|
36
|
+
onStart(span: Span, parentContext: Context): void;
|
|
37
|
+
/**
|
|
38
|
+
* Called when a span ends - this is where we calculate and add cost
|
|
39
|
+
*/
|
|
40
|
+
onEnd(span: ReadableSpan): void;
|
|
41
|
+
/**
|
|
42
|
+
* Calculate cost for a span based on usage
|
|
43
|
+
*/
|
|
44
|
+
private calculateSpanCost;
|
|
45
|
+
/**
|
|
46
|
+
* Shutdown the processor
|
|
47
|
+
*/
|
|
48
|
+
shutdown(): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Force flush (no-op for this processor)
|
|
51
|
+
*/
|
|
52
|
+
forceFlush(): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
export default TokenMeterProcessor;
|
|
55
|
+
//# sourceMappingURL=TokenMeterProcessor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenMeterProcessor.d.ts","sourceRoot":"","sources":["../../src/processor/TokenMeterProcessor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;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;AAG9E;;;;;;;;;;;;;;;GAeG;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,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,GAAG,IAAI;IAKjD;;OAEG;IACH,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAmD/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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TokenMeter Span Processor
|
|
3
|
+
*
|
|
4
|
+
* An OpenTelemetry SpanProcessor that calculates costs based on usage data
|
|
5
|
+
* and adds cost attributes to spans before they are exported.
|
|
6
|
+
*/
|
|
7
|
+
import { loadManifest, getModelPricing, calculateCost, getCachedManifest, } from "../pricing/manifest.js";
|
|
8
|
+
import { TM_ATTRIBUTES, GEN_AI_ATTRIBUTES } from "../types.js";
|
|
9
|
+
/**
|
|
10
|
+
* TokenMeter SpanProcessor
|
|
11
|
+
*
|
|
12
|
+
* Intercepts spans on end and calculates costs based on usage attributes.
|
|
13
|
+
* The calculated cost is added as an attribute before the span is exported.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
18
|
+
* import { TokenMeterProcessor } from 'tokenmeter';
|
|
19
|
+
*
|
|
20
|
+
* const provider = new NodeTracerProvider();
|
|
21
|
+
* provider.addSpanProcessor(new TokenMeterProcessor());
|
|
22
|
+
* provider.register();
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export class TokenMeterProcessor {
|
|
26
|
+
manifest = null;
|
|
27
|
+
manifestPromise = null;
|
|
28
|
+
config;
|
|
29
|
+
pricingOverrides;
|
|
30
|
+
constructor(config = {}) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.pricingOverrides = config.pricingOverrides || {};
|
|
33
|
+
// Start loading manifest in background
|
|
34
|
+
this.manifestPromise = this.loadManifestAsync();
|
|
35
|
+
}
|
|
36
|
+
async loadManifestAsync() {
|
|
37
|
+
try {
|
|
38
|
+
this.manifest = await loadManifest({
|
|
39
|
+
manifestUrl: this.config.manifestUrl,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error("[tokenmeter] Failed to load pricing manifest:", error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Called when a span starts (no-op for TokenMeter)
|
|
48
|
+
*/
|
|
49
|
+
onStart(span, parentContext) {
|
|
50
|
+
// We don't need to do anything when spans start
|
|
51
|
+
// Cost calculation happens on end
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Called when a span ends - this is where we calculate and add cost
|
|
55
|
+
*/
|
|
56
|
+
onEnd(span) {
|
|
57
|
+
// Get usage attributes from span
|
|
58
|
+
const attrs = span.attributes;
|
|
59
|
+
// Check if this span has usage data
|
|
60
|
+
const inputUnits = attrs[TM_ATTRIBUTES.INPUT_UNITS] ||
|
|
61
|
+
attrs[GEN_AI_ATTRIBUTES.INPUT_TOKENS];
|
|
62
|
+
const outputUnits = attrs[TM_ATTRIBUTES.OUTPUT_UNITS] ||
|
|
63
|
+
attrs[GEN_AI_ATTRIBUTES.OUTPUT_TOKENS];
|
|
64
|
+
// If no usage data, skip
|
|
65
|
+
if (inputUnits === undefined && outputUnits === undefined) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Get provider and model
|
|
69
|
+
const provider = attrs[TM_ATTRIBUTES.PROVIDER] ||
|
|
70
|
+
attrs[GEN_AI_ATTRIBUTES.SYSTEM] ||
|
|
71
|
+
"unknown";
|
|
72
|
+
const model = attrs[TM_ATTRIBUTES.MODEL] ||
|
|
73
|
+
attrs[GEN_AI_ATTRIBUTES.MODEL] ||
|
|
74
|
+
"unknown";
|
|
75
|
+
// Calculate cost
|
|
76
|
+
const cost = this.calculateSpanCost(provider, model, {
|
|
77
|
+
inputUnits,
|
|
78
|
+
outputUnits,
|
|
79
|
+
});
|
|
80
|
+
// Add cost attribute to span
|
|
81
|
+
// Note: OTel allows modifying span attributes after end but before export
|
|
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)
|
|
84
|
+
if (cost !== null) {
|
|
85
|
+
// Unfortunately, ReadableSpan doesn't allow setting attributes after end
|
|
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)}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Calculate cost for a span based on usage
|
|
96
|
+
*/
|
|
97
|
+
calculateSpanCost(provider, model, usage) {
|
|
98
|
+
// Check overrides first
|
|
99
|
+
if (this.pricingOverrides[provider]?.[model]) {
|
|
100
|
+
return calculateCost(usage, this.pricingOverrides[provider][model]);
|
|
101
|
+
}
|
|
102
|
+
// Fall back to manifest
|
|
103
|
+
const manifest = this.manifest || getCachedManifest();
|
|
104
|
+
if (!manifest) {
|
|
105
|
+
console.warn(`[tokenmeter] Pricing manifest not loaded, cannot calculate cost`);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const pricing = getModelPricing(provider, model, manifest);
|
|
109
|
+
if (!pricing) {
|
|
110
|
+
console.warn(`[tokenmeter] No pricing found for ${provider}/${model}`);
|
|
111
|
+
return 0; // Return 0, not null, to indicate we tried but found no pricing
|
|
112
|
+
}
|
|
113
|
+
return calculateCost(usage, pricing);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Shutdown the processor
|
|
117
|
+
*/
|
|
118
|
+
async shutdown() {
|
|
119
|
+
// Wait for manifest to finish loading
|
|
120
|
+
if (this.manifestPromise) {
|
|
121
|
+
await this.manifestPromise;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Force flush (no-op for this processor)
|
|
126
|
+
*/
|
|
127
|
+
async forceFlush() {
|
|
128
|
+
// No buffering, nothing to flush
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export default TokenMeterProcessor;
|
|
132
|
+
//# sourceMappingURL=TokenMeterProcessor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenMeterProcessor.js","sourceRoot":"","sources":["../../src/processor/TokenMeterProcessor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Client
|
|
3
|
+
*
|
|
4
|
+
* Provides cost aggregation and querying capabilities.
|
|
5
|
+
*/
|
|
6
|
+
import type { CostQueryOptions, CostResult } from "../types.js";
|
|
7
|
+
/**
|
|
8
|
+
* Query client configuration
|
|
9
|
+
*/
|
|
10
|
+
export interface QueryClientConfig {
|
|
11
|
+
/** PostgreSQL connection string */
|
|
12
|
+
connectionString: string;
|
|
13
|
+
/** Table name (default: tokenmeter_events) */
|
|
14
|
+
tableName?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Query client interface
|
|
18
|
+
*/
|
|
19
|
+
export interface QueryClient {
|
|
20
|
+
/** Get costs with optional filtering and grouping */
|
|
21
|
+
getCosts(options?: CostQueryOptions): Promise<CostResult>;
|
|
22
|
+
/** Get costs for a specific user */
|
|
23
|
+
getCostByUser(userId: string, options?: Omit<CostQueryOptions, "userId">): Promise<CostResult>;
|
|
24
|
+
/** Get costs for a specific organization */
|
|
25
|
+
getCostByOrg(orgId: string, options?: Omit<CostQueryOptions, "organizationId">): Promise<CostResult>;
|
|
26
|
+
/** Get costs for a specific model */
|
|
27
|
+
getCostByModel(model: string, options?: Omit<CostQueryOptions, "model">): Promise<CostResult>;
|
|
28
|
+
/** Get costs for a specific provider */
|
|
29
|
+
getCostByProvider(provider: string, options?: Omit<CostQueryOptions, "provider">): Promise<CostResult>;
|
|
30
|
+
/** Get total cost for a workflow/trace */
|
|
31
|
+
getWorkflowCost(workflowId: string): Promise<CostResult>;
|
|
32
|
+
/** Close the database connection */
|
|
33
|
+
close(): Promise<void>;
|
|
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
|
+
export declare function createQueryClient(config: QueryClientConfig): Promise<QueryClient>;
|
|
60
|
+
export default createQueryClient;
|
|
61
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +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;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,WAAW,CAAC,CAqOtB;AAED,eAAe,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Client
|
|
3
|
+
*
|
|
4
|
+
* Provides cost aggregation and querying capabilities.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Create a query client for cost aggregation
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { createQueryClient } from 'tokenmeter/query';
|
|
12
|
+
*
|
|
13
|
+
* const client = createQueryClient({
|
|
14
|
+
* connectionString: process.env.DATABASE_URL,
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* // Get total costs for an organization
|
|
18
|
+
* const orgCosts = await client.getCostByOrg('org_123', {
|
|
19
|
+
* from: '2024-01-01',
|
|
20
|
+
* to: '2024-01-31',
|
|
21
|
+
* groupBy: ['model'],
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* console.log(`Total: $${orgCosts.totalCost}`);
|
|
25
|
+
* for (const group of orgCosts.groups ?? []) {
|
|
26
|
+
* console.log(` ${group.key.model}: $${group.cost}`);
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export async function createQueryClient(config) {
|
|
31
|
+
const tableName = config.tableName ?? "tokenmeter_events";
|
|
32
|
+
// Dynamic import to avoid requiring pg at load time
|
|
33
|
+
const { Pool } = await import("pg");
|
|
34
|
+
const pool = new Pool({
|
|
35
|
+
connectionString: config.connectionString,
|
|
36
|
+
});
|
|
37
|
+
/**
|
|
38
|
+
* Build WHERE clause from options
|
|
39
|
+
*/
|
|
40
|
+
function buildWhereClause(options, startParamIndex = 1) {
|
|
41
|
+
const conditions = [];
|
|
42
|
+
const values = [];
|
|
43
|
+
let paramIndex = startParamIndex;
|
|
44
|
+
if (options.from) {
|
|
45
|
+
conditions.push(`created_at >= $${paramIndex}`);
|
|
46
|
+
values.push(options.from instanceof Date ? options.from : new Date(options.from));
|
|
47
|
+
paramIndex++;
|
|
48
|
+
}
|
|
49
|
+
if (options.to) {
|
|
50
|
+
conditions.push(`created_at <= $${paramIndex}`);
|
|
51
|
+
values.push(options.to instanceof Date ? options.to : new Date(options.to));
|
|
52
|
+
paramIndex++;
|
|
53
|
+
}
|
|
54
|
+
if (options.provider) {
|
|
55
|
+
conditions.push(`provider = $${paramIndex}`);
|
|
56
|
+
values.push(options.provider);
|
|
57
|
+
paramIndex++;
|
|
58
|
+
}
|
|
59
|
+
if (options.model) {
|
|
60
|
+
conditions.push(`model = $${paramIndex}`);
|
|
61
|
+
values.push(options.model);
|
|
62
|
+
paramIndex++;
|
|
63
|
+
}
|
|
64
|
+
if (options.organizationId) {
|
|
65
|
+
conditions.push(`organization_id = $${paramIndex}`);
|
|
66
|
+
values.push(options.organizationId);
|
|
67
|
+
paramIndex++;
|
|
68
|
+
}
|
|
69
|
+
if (options.userId) {
|
|
70
|
+
conditions.push(`user_id = $${paramIndex}`);
|
|
71
|
+
values.push(options.userId);
|
|
72
|
+
paramIndex++;
|
|
73
|
+
}
|
|
74
|
+
const clause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
75
|
+
return { clause, values, nextIndex: paramIndex };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Map groupBy field names to column names
|
|
79
|
+
*/
|
|
80
|
+
function mapGroupByField(field) {
|
|
81
|
+
const fieldMap = {
|
|
82
|
+
provider: "provider",
|
|
83
|
+
model: "model",
|
|
84
|
+
organizationId: "organization_id",
|
|
85
|
+
userId: "user_id",
|
|
86
|
+
workflowId: "workflow_id",
|
|
87
|
+
};
|
|
88
|
+
return fieldMap[field] ?? field;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Execute a cost query
|
|
92
|
+
*/
|
|
93
|
+
async function getCosts(options = {}) {
|
|
94
|
+
const { clause, values, nextIndex } = buildWhereClause(options);
|
|
95
|
+
const groupBy = options.groupBy?.map(mapGroupByField) ?? [];
|
|
96
|
+
let query;
|
|
97
|
+
let queryValues = values;
|
|
98
|
+
if (groupBy.length > 0) {
|
|
99
|
+
// Query with grouping
|
|
100
|
+
const groupByClause = groupBy.join(", ");
|
|
101
|
+
const selectFields = groupBy
|
|
102
|
+
.map((col) => {
|
|
103
|
+
// Map back to camelCase for the result
|
|
104
|
+
const keyName = col === "organization_id"
|
|
105
|
+
? "organizationId"
|
|
106
|
+
: col === "user_id"
|
|
107
|
+
? "userId"
|
|
108
|
+
: col === "workflow_id"
|
|
109
|
+
? "workflowId"
|
|
110
|
+
: col;
|
|
111
|
+
return `${col} as "${keyName}"`;
|
|
112
|
+
})
|
|
113
|
+
.join(", ");
|
|
114
|
+
query = `
|
|
115
|
+
SELECT
|
|
116
|
+
${selectFields},
|
|
117
|
+
COALESCE(SUM(cost_usd), 0)::numeric as cost,
|
|
118
|
+
COUNT(*)::int as count
|
|
119
|
+
FROM ${tableName}
|
|
120
|
+
${clause}
|
|
121
|
+
GROUP BY ${groupByClause}
|
|
122
|
+
ORDER BY cost DESC
|
|
123
|
+
${options.limit ? `LIMIT $${nextIndex}` : ""}
|
|
124
|
+
`;
|
|
125
|
+
if (options.limit) {
|
|
126
|
+
queryValues = [...values, options.limit];
|
|
127
|
+
}
|
|
128
|
+
const result = await pool.query(query, queryValues);
|
|
129
|
+
// Calculate totals
|
|
130
|
+
let totalCost = 0;
|
|
131
|
+
let totalCount = 0;
|
|
132
|
+
const groups = [];
|
|
133
|
+
for (const row of result.rows) {
|
|
134
|
+
const cost = parseFloat(row.cost);
|
|
135
|
+
const count = row.count;
|
|
136
|
+
totalCost += cost;
|
|
137
|
+
totalCount += count;
|
|
138
|
+
// Build key object from group fields
|
|
139
|
+
const key = {};
|
|
140
|
+
for (const field of groupBy) {
|
|
141
|
+
const keyName = field === "organization_id"
|
|
142
|
+
? "organizationId"
|
|
143
|
+
: field === "user_id"
|
|
144
|
+
? "userId"
|
|
145
|
+
: field === "workflow_id"
|
|
146
|
+
? "workflowId"
|
|
147
|
+
: field;
|
|
148
|
+
key[keyName] = row[keyName] ?? "";
|
|
149
|
+
}
|
|
150
|
+
groups.push({ key, cost, count });
|
|
151
|
+
}
|
|
152
|
+
return { totalCost, count: totalCount, groups };
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// Simple aggregate query
|
|
156
|
+
query = `
|
|
157
|
+
SELECT
|
|
158
|
+
COALESCE(SUM(cost_usd), 0)::numeric as total_cost,
|
|
159
|
+
COUNT(*)::int as count
|
|
160
|
+
FROM ${tableName}
|
|
161
|
+
${clause}
|
|
162
|
+
`;
|
|
163
|
+
const result = await pool.query(query, values);
|
|
164
|
+
const row = result.rows[0];
|
|
165
|
+
return {
|
|
166
|
+
totalCost: parseFloat(row.total_cost),
|
|
167
|
+
count: row.count,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
getCosts,
|
|
173
|
+
async getCostByUser(userId, options = {}) {
|
|
174
|
+
return getCosts({ ...options, userId });
|
|
175
|
+
},
|
|
176
|
+
async getCostByOrg(orgId, options = {}) {
|
|
177
|
+
return getCosts({ ...options, organizationId: orgId });
|
|
178
|
+
},
|
|
179
|
+
async getCostByModel(model, options = {}) {
|
|
180
|
+
return getCosts({ ...options, model });
|
|
181
|
+
},
|
|
182
|
+
async getCostByProvider(provider, options = {}) {
|
|
183
|
+
return getCosts({ ...options, provider });
|
|
184
|
+
},
|
|
185
|
+
async getWorkflowCost(workflowId) {
|
|
186
|
+
const query = `
|
|
187
|
+
SELECT
|
|
188
|
+
COALESCE(SUM(cost_usd), 0)::numeric as total_cost,
|
|
189
|
+
COUNT(*)::int as count
|
|
190
|
+
FROM ${tableName}
|
|
191
|
+
WHERE trace_id = $1
|
|
192
|
+
`;
|
|
193
|
+
const result = await pool.query(query, [workflowId]);
|
|
194
|
+
const row = result.rows[0];
|
|
195
|
+
return {
|
|
196
|
+
totalCost: parseFloat(row.total_cost),
|
|
197
|
+
count: row.count,
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
async close() {
|
|
201
|
+
await pool.end();
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
export default createQueryClient;
|
|
206
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/query/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/query/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
|