@revkeen/sdk 1.20260701.1259 → 1.20260702.1265

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/dist/ai.d.mts ADDED
@@ -0,0 +1,60 @@
1
+ import { LanguageModel } from 'ai';
2
+ import { U as UsageEvent, B as BatchIngestResult } from './types.gen-kn79GXSQ.mjs';
3
+
4
+ type UsageEventsClient = {
5
+ usage: {
6
+ ingest: (body: UsageEvent | {
7
+ events: UsageEvent[];
8
+ }) => Promise<BatchIngestResult>;
9
+ };
10
+ };
11
+ type JsonObject = Record<string, unknown>;
12
+ type AiTokenUsage = {
13
+ inputTokens?: {
14
+ total?: number;
15
+ noCache?: number;
16
+ cacheRead?: number;
17
+ cacheWrite?: number;
18
+ };
19
+ outputTokens?: {
20
+ total?: number;
21
+ text?: number;
22
+ reasoning?: number;
23
+ };
24
+ raw?: JsonObject;
25
+ };
26
+ type AiUsageQuantity = "input_tokens" | "input_tokens_no_cache" | "cached_input_tokens" | "cache_write_tokens" | "output_tokens" | "output_text_tokens" | "output_reasoning_tokens" | "total_tokens" | ((usage: AiMeterUsageContext) => number | undefined);
27
+ interface AiMeterEventMapping {
28
+ name?: string;
29
+ meterId?: string;
30
+ quantity: AiUsageQuantity;
31
+ includeZero?: boolean;
32
+ metadata?: JsonObject;
33
+ idempotencyKeySuffix?: string;
34
+ }
35
+ interface AiMeterUsageContext {
36
+ usage: AiTokenUsage;
37
+ provider: string;
38
+ model: string;
39
+ operation: "generate" | "stream";
40
+ providerMetadata?: unknown;
41
+ }
42
+ interface RevKeenAiMeteringOptions {
43
+ revkeen: UsageEventsClient;
44
+ meters: AiMeterEventMapping[];
45
+ customerId?: string;
46
+ externalCustomerId?: string;
47
+ subscriptionId?: string;
48
+ provider?: string;
49
+ model?: string;
50
+ metadata?: JsonObject | ((context: AiMeterUsageContext) => JsonObject | undefined);
51
+ idempotencyKey?: string | ((context: AiMeterUsageContext) => string | undefined);
52
+ timestamp?: string | Date | ((context: AiMeterUsageContext) => string | Date | undefined);
53
+ awaitIngest?: boolean;
54
+ onIngestError?: (error: unknown, context: AiMeterUsageContext) => void | Promise<void>;
55
+ }
56
+ declare const AI_METER_ADAPTER_GUARDRAILS: readonly ["Run only in merchant server-side or trusted edge code.", "Emit raw usage only through /v2/usage-events.", "Never compute or transmit RevKeen customer charge, provider cost, margin, tax, invoice, or accounting values.", "Never write directly to RDS, ClickHouse, Redpanda, Kafka, or any analytics infrastructure."];
57
+ declare function buildAiUsageEvents(options: RevKeenAiMeteringOptions, context: AiMeterUsageContext): UsageEvent[];
58
+ declare function withRevKeenAiMetering(model: LanguageModel, options: RevKeenAiMeteringOptions): LanguageModel;
59
+
60
+ export { AI_METER_ADAPTER_GUARDRAILS, type AiMeterEventMapping, type AiMeterUsageContext, type AiUsageQuantity, type RevKeenAiMeteringOptions, buildAiUsageEvents, withRevKeenAiMetering };
package/dist/ai.d.ts ADDED
@@ -0,0 +1,60 @@
1
+ import { LanguageModel } from 'ai';
2
+ import { U as UsageEvent, B as BatchIngestResult } from './types.gen-kn79GXSQ.js';
3
+
4
+ type UsageEventsClient = {
5
+ usage: {
6
+ ingest: (body: UsageEvent | {
7
+ events: UsageEvent[];
8
+ }) => Promise<BatchIngestResult>;
9
+ };
10
+ };
11
+ type JsonObject = Record<string, unknown>;
12
+ type AiTokenUsage = {
13
+ inputTokens?: {
14
+ total?: number;
15
+ noCache?: number;
16
+ cacheRead?: number;
17
+ cacheWrite?: number;
18
+ };
19
+ outputTokens?: {
20
+ total?: number;
21
+ text?: number;
22
+ reasoning?: number;
23
+ };
24
+ raw?: JsonObject;
25
+ };
26
+ type AiUsageQuantity = "input_tokens" | "input_tokens_no_cache" | "cached_input_tokens" | "cache_write_tokens" | "output_tokens" | "output_text_tokens" | "output_reasoning_tokens" | "total_tokens" | ((usage: AiMeterUsageContext) => number | undefined);
27
+ interface AiMeterEventMapping {
28
+ name?: string;
29
+ meterId?: string;
30
+ quantity: AiUsageQuantity;
31
+ includeZero?: boolean;
32
+ metadata?: JsonObject;
33
+ idempotencyKeySuffix?: string;
34
+ }
35
+ interface AiMeterUsageContext {
36
+ usage: AiTokenUsage;
37
+ provider: string;
38
+ model: string;
39
+ operation: "generate" | "stream";
40
+ providerMetadata?: unknown;
41
+ }
42
+ interface RevKeenAiMeteringOptions {
43
+ revkeen: UsageEventsClient;
44
+ meters: AiMeterEventMapping[];
45
+ customerId?: string;
46
+ externalCustomerId?: string;
47
+ subscriptionId?: string;
48
+ provider?: string;
49
+ model?: string;
50
+ metadata?: JsonObject | ((context: AiMeterUsageContext) => JsonObject | undefined);
51
+ idempotencyKey?: string | ((context: AiMeterUsageContext) => string | undefined);
52
+ timestamp?: string | Date | ((context: AiMeterUsageContext) => string | Date | undefined);
53
+ awaitIngest?: boolean;
54
+ onIngestError?: (error: unknown, context: AiMeterUsageContext) => void | Promise<void>;
55
+ }
56
+ declare const AI_METER_ADAPTER_GUARDRAILS: readonly ["Run only in merchant server-side or trusted edge code.", "Emit raw usage only through /v2/usage-events.", "Never compute or transmit RevKeen customer charge, provider cost, margin, tax, invoice, or accounting values.", "Never write directly to RDS, ClickHouse, Redpanda, Kafka, or any analytics infrastructure."];
57
+ declare function buildAiUsageEvents(options: RevKeenAiMeteringOptions, context: AiMeterUsageContext): UsageEvent[];
58
+ declare function withRevKeenAiMetering(model: LanguageModel, options: RevKeenAiMeteringOptions): LanguageModel;
59
+
60
+ export { AI_METER_ADAPTER_GUARDRAILS, type AiMeterEventMapping, type AiMeterUsageContext, type AiUsageQuantity, type RevKeenAiMeteringOptions, buildAiUsageEvents, withRevKeenAiMetering };
package/dist/ai.js ADDED
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/ai.ts
21
+ var ai_exports = {};
22
+ __export(ai_exports, {
23
+ AI_METER_ADAPTER_GUARDRAILS: () => AI_METER_ADAPTER_GUARDRAILS,
24
+ buildAiUsageEvents: () => buildAiUsageEvents,
25
+ withRevKeenAiMetering: () => withRevKeenAiMetering
26
+ });
27
+ module.exports = __toCommonJS(ai_exports);
28
+ var import_ai = require("ai");
29
+ var AI_METER_ADAPTER_GUARDRAILS = [
30
+ "Run only in merchant server-side or trusted edge code.",
31
+ "Emit raw usage only through /v2/usage-events.",
32
+ "Never compute or transmit RevKeen customer charge, provider cost, margin, tax, invoice, or accounting values.",
33
+ "Never write directly to RDS, ClickHouse, Redpanda, Kafka, or any analytics infrastructure."
34
+ ];
35
+ function isRecord(value) {
36
+ return typeof value === "object" && value !== null && !Array.isArray(value);
37
+ }
38
+ function finiteNumber(value) {
39
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
40
+ }
41
+ function sumDefined(values) {
42
+ const defined = values.filter((value) => value !== void 0);
43
+ if (defined.length === 0) return void 0;
44
+ return defined.reduce((total, value) => total + value, 0);
45
+ }
46
+ function quantityFor(selection, context) {
47
+ if (typeof selection === "function") {
48
+ return finiteNumber(selection(context));
49
+ }
50
+ const { usage } = context;
51
+ switch (selection) {
52
+ case "input_tokens":
53
+ return finiteNumber(usage.inputTokens?.total);
54
+ case "input_tokens_no_cache":
55
+ return finiteNumber(usage.inputTokens?.noCache);
56
+ case "cached_input_tokens":
57
+ return finiteNumber(usage.inputTokens?.cacheRead);
58
+ case "cache_write_tokens":
59
+ return finiteNumber(usage.inputTokens?.cacheWrite);
60
+ case "output_tokens":
61
+ return finiteNumber(usage.outputTokens?.total);
62
+ case "output_text_tokens":
63
+ return finiteNumber(usage.outputTokens?.text);
64
+ case "output_reasoning_tokens":
65
+ return finiteNumber(usage.outputTokens?.reasoning);
66
+ case "total_tokens":
67
+ return sumDefined([
68
+ usage.inputTokens?.total,
69
+ usage.outputTokens?.total
70
+ ]);
71
+ }
72
+ }
73
+ function normalizeUsage(usage) {
74
+ return {
75
+ input_tokens: {
76
+ total: usage.inputTokens?.total ?? null,
77
+ no_cache: usage.inputTokens?.noCache ?? null,
78
+ cache_read: usage.inputTokens?.cacheRead ?? null,
79
+ cache_write: usage.inputTokens?.cacheWrite ?? null
80
+ },
81
+ output_tokens: {
82
+ total: usage.outputTokens?.total ?? null,
83
+ text: usage.outputTokens?.text ?? null,
84
+ reasoning: usage.outputTokens?.reasoning ?? null
85
+ }
86
+ };
87
+ }
88
+ function eventNameFor(mapping) {
89
+ if (mapping.name) return mapping.name;
90
+ if (mapping.meterId) return "ai_usage";
91
+ throw new Error("AI meter mapping requires either name or meterId");
92
+ }
93
+ function assertServerSideRuntime() {
94
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
95
+ throw new Error(
96
+ "RevKeen AI Meter Adapter must run in trusted server-side or edge code, not in browser code."
97
+ );
98
+ }
99
+ }
100
+ function valueFromOption(option, context) {
101
+ return typeof option === "function" ? option(context) : option;
102
+ }
103
+ function buildAiUsageEvents(options, context) {
104
+ if (options.meters.length === 0) {
105
+ throw new Error("RevKeen AI Meter Adapter requires at least one meter mapping");
106
+ }
107
+ const baseMetadata = valueFromOption(options.metadata, context);
108
+ const timestamp = valueFromOption(options.timestamp, context);
109
+ const baseIdempotencyKey = valueFromOption(options.idempotencyKey, context);
110
+ const normalizedUsage = normalizeUsage(context.usage);
111
+ return options.meters.flatMap((mapping) => {
112
+ const quantity = quantityFor(mapping.quantity, context);
113
+ if (quantity === void 0 || !mapping.includeZero && quantity === 0) {
114
+ return [];
115
+ }
116
+ const idempotencySuffix = mapping.idempotencyKeySuffix ?? (typeof mapping.quantity === "string" ? mapping.quantity : eventNameFor(mapping));
117
+ return [{
118
+ name: eventNameFor(mapping),
119
+ meter_id: mapping.meterId,
120
+ customer_id: options.customerId,
121
+ external_customer_id: options.externalCustomerId,
122
+ subscription_id: options.subscriptionId,
123
+ quantity,
124
+ timestamp: timestamp instanceof Date ? timestamp.toISOString() : timestamp,
125
+ idempotency_key: baseIdempotencyKey ? `${baseIdempotencyKey}:${idempotencySuffix}` : void 0,
126
+ metadata: {
127
+ ...isRecord(baseMetadata) ? baseMetadata : {},
128
+ ...isRecord(mapping.metadata) ? mapping.metadata : {},
129
+ revkeen: {
130
+ adapter: "@revkeen/sdk/ai",
131
+ schema_version: 1,
132
+ source: "ai_meter_adapter"
133
+ },
134
+ ai: {
135
+ provider: context.provider,
136
+ model: context.model,
137
+ operation: context.operation,
138
+ usage: normalizedUsage,
139
+ provider_hints: {
140
+ usage_raw: context.usage.raw ?? null,
141
+ provider_metadata: context.providerMetadata ?? null
142
+ }
143
+ }
144
+ }
145
+ }];
146
+ });
147
+ }
148
+ async function emitUsage(options, context) {
149
+ const events = buildAiUsageEvents(options, context);
150
+ if (events.length === 0) return;
151
+ await options.revkeen.usage.ingest(events.length === 1 ? events[0] : { events });
152
+ }
153
+ function resolveProvider(model, fallback) {
154
+ return fallback ?? (typeof model === "string" ? "unknown" : model.provider ?? "unknown");
155
+ }
156
+ function resolveModel(model, fallback) {
157
+ return fallback ?? (typeof model === "string" ? model : model.modelId ?? "unknown");
158
+ }
159
+ function createMiddleware(options) {
160
+ return {
161
+ specificationVersion: "v3",
162
+ wrapGenerate: async ({ doGenerate, model }) => {
163
+ const result = await doGenerate();
164
+ const context = {
165
+ usage: result.usage,
166
+ provider: options.provider ?? model.provider ?? "unknown",
167
+ model: options.model ?? model.modelId ?? "unknown",
168
+ operation: "generate",
169
+ providerMetadata: result.providerMetadata
170
+ };
171
+ const ingest = emitUsage(options, context).catch(async (error) => {
172
+ await options.onIngestError?.(error, context);
173
+ if (options.awaitIngest !== false) throw error;
174
+ });
175
+ if (options.awaitIngest !== false) {
176
+ await ingest;
177
+ }
178
+ return result;
179
+ },
180
+ wrapStream: async ({ doStream, model }) => {
181
+ const result = await doStream();
182
+ const stream = result.stream.pipeThrough(new TransformStream({
183
+ async transform(part, controller) {
184
+ controller.enqueue(part);
185
+ if (part.type !== "finish") return;
186
+ const context = {
187
+ usage: part.usage,
188
+ provider: options.provider ?? model.provider ?? "unknown",
189
+ model: options.model ?? model.modelId ?? "unknown",
190
+ operation: "stream",
191
+ providerMetadata: part.providerMetadata
192
+ };
193
+ const ingest = emitUsage(options, context).catch(async (error) => {
194
+ await options.onIngestError?.(error, context);
195
+ if (options.awaitIngest !== false) throw error;
196
+ });
197
+ if (options.awaitIngest !== false) {
198
+ await ingest;
199
+ }
200
+ }
201
+ }));
202
+ return {
203
+ ...result,
204
+ stream
205
+ };
206
+ }
207
+ };
208
+ }
209
+ function withRevKeenAiMetering(model, options) {
210
+ assertServerSideRuntime();
211
+ if (typeof model === "string") {
212
+ throw new Error("RevKeen AI Meter Adapter requires an AI SDK language model object, not a model id string.");
213
+ }
214
+ return (0, import_ai.wrapLanguageModel)({
215
+ model,
216
+ middleware: createMiddleware(options),
217
+ providerId: resolveProvider(model, options.provider),
218
+ modelId: resolveModel(model, options.model)
219
+ });
220
+ }
221
+ // Annotate the CommonJS export names for ESM import in node:
222
+ 0 && (module.exports = {
223
+ AI_METER_ADAPTER_GUARDRAILS,
224
+ buildAiUsageEvents,
225
+ withRevKeenAiMetering
226
+ });
227
+ //# sourceMappingURL=ai.js.map
package/dist/ai.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ai.ts"],"sourcesContent":["import {\n wrapLanguageModel,\n type LanguageModel,\n type LanguageModelMiddleware,\n} from \"ai\";\nimport type { BatchIngestResult, UsageEvent } from \"./generated/types.gen.js\";\n\ntype UsageEventsClient = {\n usage: {\n ingest: (body: UsageEvent | { events: UsageEvent[] }) => Promise<BatchIngestResult>;\n };\n};\n\ntype JsonObject = Record<string, unknown>;\n\ntype AiTokenUsage = {\n inputTokens?: {\n total?: number;\n noCache?: number;\n cacheRead?: number;\n cacheWrite?: number;\n };\n outputTokens?: {\n total?: number;\n text?: number;\n reasoning?: number;\n };\n raw?: JsonObject;\n};\n\nexport type AiUsageQuantity =\n | \"input_tokens\"\n | \"input_tokens_no_cache\"\n | \"cached_input_tokens\"\n | \"cache_write_tokens\"\n | \"output_tokens\"\n | \"output_text_tokens\"\n | \"output_reasoning_tokens\"\n | \"total_tokens\"\n | ((usage: AiMeterUsageContext) => number | undefined);\n\nexport interface AiMeterEventMapping {\n name?: string;\n meterId?: string;\n quantity: AiUsageQuantity;\n includeZero?: boolean;\n metadata?: JsonObject;\n idempotencyKeySuffix?: string;\n}\n\nexport interface AiMeterUsageContext {\n usage: AiTokenUsage;\n provider: string;\n model: string;\n operation: \"generate\" | \"stream\";\n providerMetadata?: unknown;\n}\n\nexport interface RevKeenAiMeteringOptions {\n revkeen: UsageEventsClient;\n meters: AiMeterEventMapping[];\n customerId?: string;\n externalCustomerId?: string;\n subscriptionId?: string;\n provider?: string;\n model?: string;\n metadata?: JsonObject | ((context: AiMeterUsageContext) => JsonObject | undefined);\n idempotencyKey?: string | ((context: AiMeterUsageContext) => string | undefined);\n timestamp?: string | Date | ((context: AiMeterUsageContext) => string | Date | undefined);\n awaitIngest?: boolean;\n onIngestError?: (error: unknown, context: AiMeterUsageContext) => void | Promise<void>;\n}\n\nexport const AI_METER_ADAPTER_GUARDRAILS = [\n \"Run only in merchant server-side or trusted edge code.\",\n \"Emit raw usage only through /v2/usage-events.\",\n \"Never compute or transmit RevKeen customer charge, provider cost, margin, tax, invoice, or accounting values.\",\n \"Never write directly to RDS, ClickHouse, Redpanda, Kafka, or any analytics infrastructure.\",\n] as const;\n\nfunction isRecord(value: unknown): value is JsonObject {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction finiteNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nfunction sumDefined(values: Array<number | undefined>): number | undefined {\n const defined = values.filter((value): value is number => value !== undefined);\n if (defined.length === 0) return undefined;\n return defined.reduce((total, value) => total + value, 0);\n}\n\nfunction quantityFor(selection: AiUsageQuantity, context: AiMeterUsageContext): number | undefined {\n if (typeof selection === \"function\") {\n return finiteNumber(selection(context));\n }\n\n const { usage } = context;\n switch (selection) {\n case \"input_tokens\":\n return finiteNumber(usage.inputTokens?.total);\n case \"input_tokens_no_cache\":\n return finiteNumber(usage.inputTokens?.noCache);\n case \"cached_input_tokens\":\n return finiteNumber(usage.inputTokens?.cacheRead);\n case \"cache_write_tokens\":\n return finiteNumber(usage.inputTokens?.cacheWrite);\n case \"output_tokens\":\n return finiteNumber(usage.outputTokens?.total);\n case \"output_text_tokens\":\n return finiteNumber(usage.outputTokens?.text);\n case \"output_reasoning_tokens\":\n return finiteNumber(usage.outputTokens?.reasoning);\n case \"total_tokens\":\n return sumDefined([\n usage.inputTokens?.total,\n usage.outputTokens?.total,\n ]);\n }\n}\n\nfunction normalizeUsage(usage: AiTokenUsage) {\n return {\n input_tokens: {\n total: usage.inputTokens?.total ?? null,\n no_cache: usage.inputTokens?.noCache ?? null,\n cache_read: usage.inputTokens?.cacheRead ?? null,\n cache_write: usage.inputTokens?.cacheWrite ?? null,\n },\n output_tokens: {\n total: usage.outputTokens?.total ?? null,\n text: usage.outputTokens?.text ?? null,\n reasoning: usage.outputTokens?.reasoning ?? null,\n },\n };\n}\n\nfunction eventNameFor(mapping: AiMeterEventMapping) {\n if (mapping.name) return mapping.name;\n if (mapping.meterId) return \"ai_usage\";\n throw new Error(\"AI meter mapping requires either name or meterId\");\n}\n\nfunction assertServerSideRuntime() {\n if (typeof window !== \"undefined\" && typeof document !== \"undefined\") {\n throw new Error(\n \"RevKeen AI Meter Adapter must run in trusted server-side or edge code, not in browser code.\"\n );\n }\n}\n\nfunction valueFromOption<T>(\n option: T | ((context: AiMeterUsageContext) => T | undefined) | undefined,\n context: AiMeterUsageContext\n): T | undefined {\n return typeof option === \"function\"\n ? (option as (context: AiMeterUsageContext) => T | undefined)(context)\n : option;\n}\n\nexport function buildAiUsageEvents(\n options: RevKeenAiMeteringOptions,\n context: AiMeterUsageContext\n): UsageEvent[] {\n if (options.meters.length === 0) {\n throw new Error(\"RevKeen AI Meter Adapter requires at least one meter mapping\");\n }\n\n const baseMetadata = valueFromOption(options.metadata, context);\n const timestamp = valueFromOption(options.timestamp, context);\n const baseIdempotencyKey = valueFromOption(options.idempotencyKey, context);\n const normalizedUsage = normalizeUsage(context.usage);\n\n return options.meters.flatMap((mapping) => {\n const quantity = quantityFor(mapping.quantity, context);\n if (quantity === undefined || (!mapping.includeZero && quantity === 0)) {\n return [];\n }\n\n const idempotencySuffix =\n mapping.idempotencyKeySuffix ??\n (typeof mapping.quantity === \"string\" ? mapping.quantity : eventNameFor(mapping));\n\n return [{\n name: eventNameFor(mapping),\n meter_id: mapping.meterId,\n customer_id: options.customerId,\n external_customer_id: options.externalCustomerId,\n subscription_id: options.subscriptionId,\n quantity,\n timestamp: timestamp instanceof Date ? timestamp.toISOString() : timestamp,\n idempotency_key: baseIdempotencyKey\n ? `${baseIdempotencyKey}:${idempotencySuffix}`\n : undefined,\n metadata: {\n ...(isRecord(baseMetadata) ? baseMetadata : {}),\n ...(isRecord(mapping.metadata) ? mapping.metadata : {}),\n revkeen: {\n adapter: \"@revkeen/sdk/ai\",\n schema_version: 1,\n source: \"ai_meter_adapter\",\n },\n ai: {\n provider: context.provider,\n model: context.model,\n operation: context.operation,\n usage: normalizedUsage,\n provider_hints: {\n usage_raw: context.usage.raw ?? null,\n provider_metadata: context.providerMetadata ?? null,\n },\n },\n },\n }];\n });\n}\n\nasync function emitUsage(options: RevKeenAiMeteringOptions, context: AiMeterUsageContext) {\n const events = buildAiUsageEvents(options, context);\n if (events.length === 0) return;\n\n await options.revkeen.usage.ingest(events.length === 1 ? events[0] : { events });\n}\n\nfunction resolveProvider(model: LanguageModel, fallback?: string): string {\n return fallback ?? (typeof model === \"string\" ? \"unknown\" : (model as { provider?: string }).provider ?? \"unknown\");\n}\n\nfunction resolveModel(model: LanguageModel, fallback?: string): string {\n return fallback ?? (typeof model === \"string\" ? model : (model as { modelId?: string }).modelId ?? \"unknown\");\n}\n\nfunction createMiddleware(options: RevKeenAiMeteringOptions): LanguageModelMiddleware {\n return {\n specificationVersion: \"v3\",\n wrapGenerate: async ({ doGenerate, model }) => {\n const result = await doGenerate();\n const context: AiMeterUsageContext = {\n usage: result.usage,\n provider: options.provider ?? model.provider ?? \"unknown\",\n model: options.model ?? model.modelId ?? \"unknown\",\n operation: \"generate\",\n providerMetadata: result.providerMetadata,\n };\n\n const ingest = emitUsage(options, context).catch(async (error) => {\n await options.onIngestError?.(error, context);\n if (options.awaitIngest !== false) throw error;\n });\n\n if (options.awaitIngest !== false) {\n await ingest;\n }\n\n return result;\n },\n wrapStream: async ({ doStream, model }) => {\n const result = await doStream();\n const stream = result.stream.pipeThrough(new TransformStream({\n async transform(part, controller) {\n controller.enqueue(part);\n\n if (part.type !== \"finish\") return;\n\n const context: AiMeterUsageContext = {\n usage: part.usage,\n provider: options.provider ?? model.provider ?? \"unknown\",\n model: options.model ?? model.modelId ?? \"unknown\",\n operation: \"stream\",\n providerMetadata: part.providerMetadata,\n };\n\n const ingest = emitUsage(options, context).catch(async (error) => {\n await options.onIngestError?.(error, context);\n if (options.awaitIngest !== false) throw error;\n });\n\n if (options.awaitIngest !== false) {\n await ingest;\n }\n },\n }));\n\n return {\n ...result,\n stream,\n };\n },\n };\n}\n\nexport function withRevKeenAiMetering(\n model: LanguageModel,\n options: RevKeenAiMeteringOptions\n): LanguageModel {\n assertServerSideRuntime();\n\n if (typeof model === \"string\") {\n throw new Error(\"RevKeen AI Meter Adapter requires an AI SDK language model object, not a model id string.\");\n }\n\n return wrapLanguageModel({\n model: model as Parameters<typeof wrapLanguageModel>[0][\"model\"],\n middleware: createMiddleware(options),\n providerId: resolveProvider(model, options.provider),\n modelId: resolveModel(model, options.model),\n }) as LanguageModel;\n}\n"],"mappings":"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","names":[]}
package/dist/ai.mjs ADDED
@@ -0,0 +1,202 @@
1
+ // src/ai.ts
2
+ import {
3
+ wrapLanguageModel
4
+ } from "ai";
5
+ var AI_METER_ADAPTER_GUARDRAILS = [
6
+ "Run only in merchant server-side or trusted edge code.",
7
+ "Emit raw usage only through /v2/usage-events.",
8
+ "Never compute or transmit RevKeen customer charge, provider cost, margin, tax, invoice, or accounting values.",
9
+ "Never write directly to RDS, ClickHouse, Redpanda, Kafka, or any analytics infrastructure."
10
+ ];
11
+ function isRecord(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ }
14
+ function finiteNumber(value) {
15
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
16
+ }
17
+ function sumDefined(values) {
18
+ const defined = values.filter((value) => value !== void 0);
19
+ if (defined.length === 0) return void 0;
20
+ return defined.reduce((total, value) => total + value, 0);
21
+ }
22
+ function quantityFor(selection, context) {
23
+ if (typeof selection === "function") {
24
+ return finiteNumber(selection(context));
25
+ }
26
+ const { usage } = context;
27
+ switch (selection) {
28
+ case "input_tokens":
29
+ return finiteNumber(usage.inputTokens?.total);
30
+ case "input_tokens_no_cache":
31
+ return finiteNumber(usage.inputTokens?.noCache);
32
+ case "cached_input_tokens":
33
+ return finiteNumber(usage.inputTokens?.cacheRead);
34
+ case "cache_write_tokens":
35
+ return finiteNumber(usage.inputTokens?.cacheWrite);
36
+ case "output_tokens":
37
+ return finiteNumber(usage.outputTokens?.total);
38
+ case "output_text_tokens":
39
+ return finiteNumber(usage.outputTokens?.text);
40
+ case "output_reasoning_tokens":
41
+ return finiteNumber(usage.outputTokens?.reasoning);
42
+ case "total_tokens":
43
+ return sumDefined([
44
+ usage.inputTokens?.total,
45
+ usage.outputTokens?.total
46
+ ]);
47
+ }
48
+ }
49
+ function normalizeUsage(usage) {
50
+ return {
51
+ input_tokens: {
52
+ total: usage.inputTokens?.total ?? null,
53
+ no_cache: usage.inputTokens?.noCache ?? null,
54
+ cache_read: usage.inputTokens?.cacheRead ?? null,
55
+ cache_write: usage.inputTokens?.cacheWrite ?? null
56
+ },
57
+ output_tokens: {
58
+ total: usage.outputTokens?.total ?? null,
59
+ text: usage.outputTokens?.text ?? null,
60
+ reasoning: usage.outputTokens?.reasoning ?? null
61
+ }
62
+ };
63
+ }
64
+ function eventNameFor(mapping) {
65
+ if (mapping.name) return mapping.name;
66
+ if (mapping.meterId) return "ai_usage";
67
+ throw new Error("AI meter mapping requires either name or meterId");
68
+ }
69
+ function assertServerSideRuntime() {
70
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
71
+ throw new Error(
72
+ "RevKeen AI Meter Adapter must run in trusted server-side or edge code, not in browser code."
73
+ );
74
+ }
75
+ }
76
+ function valueFromOption(option, context) {
77
+ return typeof option === "function" ? option(context) : option;
78
+ }
79
+ function buildAiUsageEvents(options, context) {
80
+ if (options.meters.length === 0) {
81
+ throw new Error("RevKeen AI Meter Adapter requires at least one meter mapping");
82
+ }
83
+ const baseMetadata = valueFromOption(options.metadata, context);
84
+ const timestamp = valueFromOption(options.timestamp, context);
85
+ const baseIdempotencyKey = valueFromOption(options.idempotencyKey, context);
86
+ const normalizedUsage = normalizeUsage(context.usage);
87
+ return options.meters.flatMap((mapping) => {
88
+ const quantity = quantityFor(mapping.quantity, context);
89
+ if (quantity === void 0 || !mapping.includeZero && quantity === 0) {
90
+ return [];
91
+ }
92
+ const idempotencySuffix = mapping.idempotencyKeySuffix ?? (typeof mapping.quantity === "string" ? mapping.quantity : eventNameFor(mapping));
93
+ return [{
94
+ name: eventNameFor(mapping),
95
+ meter_id: mapping.meterId,
96
+ customer_id: options.customerId,
97
+ external_customer_id: options.externalCustomerId,
98
+ subscription_id: options.subscriptionId,
99
+ quantity,
100
+ timestamp: timestamp instanceof Date ? timestamp.toISOString() : timestamp,
101
+ idempotency_key: baseIdempotencyKey ? `${baseIdempotencyKey}:${idempotencySuffix}` : void 0,
102
+ metadata: {
103
+ ...isRecord(baseMetadata) ? baseMetadata : {},
104
+ ...isRecord(mapping.metadata) ? mapping.metadata : {},
105
+ revkeen: {
106
+ adapter: "@revkeen/sdk/ai",
107
+ schema_version: 1,
108
+ source: "ai_meter_adapter"
109
+ },
110
+ ai: {
111
+ provider: context.provider,
112
+ model: context.model,
113
+ operation: context.operation,
114
+ usage: normalizedUsage,
115
+ provider_hints: {
116
+ usage_raw: context.usage.raw ?? null,
117
+ provider_metadata: context.providerMetadata ?? null
118
+ }
119
+ }
120
+ }
121
+ }];
122
+ });
123
+ }
124
+ async function emitUsage(options, context) {
125
+ const events = buildAiUsageEvents(options, context);
126
+ if (events.length === 0) return;
127
+ await options.revkeen.usage.ingest(events.length === 1 ? events[0] : { events });
128
+ }
129
+ function resolveProvider(model, fallback) {
130
+ return fallback ?? (typeof model === "string" ? "unknown" : model.provider ?? "unknown");
131
+ }
132
+ function resolveModel(model, fallback) {
133
+ return fallback ?? (typeof model === "string" ? model : model.modelId ?? "unknown");
134
+ }
135
+ function createMiddleware(options) {
136
+ return {
137
+ specificationVersion: "v3",
138
+ wrapGenerate: async ({ doGenerate, model }) => {
139
+ const result = await doGenerate();
140
+ const context = {
141
+ usage: result.usage,
142
+ provider: options.provider ?? model.provider ?? "unknown",
143
+ model: options.model ?? model.modelId ?? "unknown",
144
+ operation: "generate",
145
+ providerMetadata: result.providerMetadata
146
+ };
147
+ const ingest = emitUsage(options, context).catch(async (error) => {
148
+ await options.onIngestError?.(error, context);
149
+ if (options.awaitIngest !== false) throw error;
150
+ });
151
+ if (options.awaitIngest !== false) {
152
+ await ingest;
153
+ }
154
+ return result;
155
+ },
156
+ wrapStream: async ({ doStream, model }) => {
157
+ const result = await doStream();
158
+ const stream = result.stream.pipeThrough(new TransformStream({
159
+ async transform(part, controller) {
160
+ controller.enqueue(part);
161
+ if (part.type !== "finish") return;
162
+ const context = {
163
+ usage: part.usage,
164
+ provider: options.provider ?? model.provider ?? "unknown",
165
+ model: options.model ?? model.modelId ?? "unknown",
166
+ operation: "stream",
167
+ providerMetadata: part.providerMetadata
168
+ };
169
+ const ingest = emitUsage(options, context).catch(async (error) => {
170
+ await options.onIngestError?.(error, context);
171
+ if (options.awaitIngest !== false) throw error;
172
+ });
173
+ if (options.awaitIngest !== false) {
174
+ await ingest;
175
+ }
176
+ }
177
+ }));
178
+ return {
179
+ ...result,
180
+ stream
181
+ };
182
+ }
183
+ };
184
+ }
185
+ function withRevKeenAiMetering(model, options) {
186
+ assertServerSideRuntime();
187
+ if (typeof model === "string") {
188
+ throw new Error("RevKeen AI Meter Adapter requires an AI SDK language model object, not a model id string.");
189
+ }
190
+ return wrapLanguageModel({
191
+ model,
192
+ middleware: createMiddleware(options),
193
+ providerId: resolveProvider(model, options.provider),
194
+ modelId: resolveModel(model, options.model)
195
+ });
196
+ }
197
+ export {
198
+ AI_METER_ADAPTER_GUARDRAILS,
199
+ buildAiUsageEvents,
200
+ withRevKeenAiMetering
201
+ };
202
+ //# sourceMappingURL=ai.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ai.ts"],"sourcesContent":["import {\n wrapLanguageModel,\n type LanguageModel,\n type LanguageModelMiddleware,\n} from \"ai\";\nimport type { BatchIngestResult, UsageEvent } from \"./generated/types.gen.js\";\n\ntype UsageEventsClient = {\n usage: {\n ingest: (body: UsageEvent | { events: UsageEvent[] }) => Promise<BatchIngestResult>;\n };\n};\n\ntype JsonObject = Record<string, unknown>;\n\ntype AiTokenUsage = {\n inputTokens?: {\n total?: number;\n noCache?: number;\n cacheRead?: number;\n cacheWrite?: number;\n };\n outputTokens?: {\n total?: number;\n text?: number;\n reasoning?: number;\n };\n raw?: JsonObject;\n};\n\nexport type AiUsageQuantity =\n | \"input_tokens\"\n | \"input_tokens_no_cache\"\n | \"cached_input_tokens\"\n | \"cache_write_tokens\"\n | \"output_tokens\"\n | \"output_text_tokens\"\n | \"output_reasoning_tokens\"\n | \"total_tokens\"\n | ((usage: AiMeterUsageContext) => number | undefined);\n\nexport interface AiMeterEventMapping {\n name?: string;\n meterId?: string;\n quantity: AiUsageQuantity;\n includeZero?: boolean;\n metadata?: JsonObject;\n idempotencyKeySuffix?: string;\n}\n\nexport interface AiMeterUsageContext {\n usage: AiTokenUsage;\n provider: string;\n model: string;\n operation: \"generate\" | \"stream\";\n providerMetadata?: unknown;\n}\n\nexport interface RevKeenAiMeteringOptions {\n revkeen: UsageEventsClient;\n meters: AiMeterEventMapping[];\n customerId?: string;\n externalCustomerId?: string;\n subscriptionId?: string;\n provider?: string;\n model?: string;\n metadata?: JsonObject | ((context: AiMeterUsageContext) => JsonObject | undefined);\n idempotencyKey?: string | ((context: AiMeterUsageContext) => string | undefined);\n timestamp?: string | Date | ((context: AiMeterUsageContext) => string | Date | undefined);\n awaitIngest?: boolean;\n onIngestError?: (error: unknown, context: AiMeterUsageContext) => void | Promise<void>;\n}\n\nexport const AI_METER_ADAPTER_GUARDRAILS = [\n \"Run only in merchant server-side or trusted edge code.\",\n \"Emit raw usage only through /v2/usage-events.\",\n \"Never compute or transmit RevKeen customer charge, provider cost, margin, tax, invoice, or accounting values.\",\n \"Never write directly to RDS, ClickHouse, Redpanda, Kafka, or any analytics infrastructure.\",\n] as const;\n\nfunction isRecord(value: unknown): value is JsonObject {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction finiteNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nfunction sumDefined(values: Array<number | undefined>): number | undefined {\n const defined = values.filter((value): value is number => value !== undefined);\n if (defined.length === 0) return undefined;\n return defined.reduce((total, value) => total + value, 0);\n}\n\nfunction quantityFor(selection: AiUsageQuantity, context: AiMeterUsageContext): number | undefined {\n if (typeof selection === \"function\") {\n return finiteNumber(selection(context));\n }\n\n const { usage } = context;\n switch (selection) {\n case \"input_tokens\":\n return finiteNumber(usage.inputTokens?.total);\n case \"input_tokens_no_cache\":\n return finiteNumber(usage.inputTokens?.noCache);\n case \"cached_input_tokens\":\n return finiteNumber(usage.inputTokens?.cacheRead);\n case \"cache_write_tokens\":\n return finiteNumber(usage.inputTokens?.cacheWrite);\n case \"output_tokens\":\n return finiteNumber(usage.outputTokens?.total);\n case \"output_text_tokens\":\n return finiteNumber(usage.outputTokens?.text);\n case \"output_reasoning_tokens\":\n return finiteNumber(usage.outputTokens?.reasoning);\n case \"total_tokens\":\n return sumDefined([\n usage.inputTokens?.total,\n usage.outputTokens?.total,\n ]);\n }\n}\n\nfunction normalizeUsage(usage: AiTokenUsage) {\n return {\n input_tokens: {\n total: usage.inputTokens?.total ?? null,\n no_cache: usage.inputTokens?.noCache ?? null,\n cache_read: usage.inputTokens?.cacheRead ?? null,\n cache_write: usage.inputTokens?.cacheWrite ?? null,\n },\n output_tokens: {\n total: usage.outputTokens?.total ?? null,\n text: usage.outputTokens?.text ?? null,\n reasoning: usage.outputTokens?.reasoning ?? null,\n },\n };\n}\n\nfunction eventNameFor(mapping: AiMeterEventMapping) {\n if (mapping.name) return mapping.name;\n if (mapping.meterId) return \"ai_usage\";\n throw new Error(\"AI meter mapping requires either name or meterId\");\n}\n\nfunction assertServerSideRuntime() {\n if (typeof window !== \"undefined\" && typeof document !== \"undefined\") {\n throw new Error(\n \"RevKeen AI Meter Adapter must run in trusted server-side or edge code, not in browser code.\"\n );\n }\n}\n\nfunction valueFromOption<T>(\n option: T | ((context: AiMeterUsageContext) => T | undefined) | undefined,\n context: AiMeterUsageContext\n): T | undefined {\n return typeof option === \"function\"\n ? (option as (context: AiMeterUsageContext) => T | undefined)(context)\n : option;\n}\n\nexport function buildAiUsageEvents(\n options: RevKeenAiMeteringOptions,\n context: AiMeterUsageContext\n): UsageEvent[] {\n if (options.meters.length === 0) {\n throw new Error(\"RevKeen AI Meter Adapter requires at least one meter mapping\");\n }\n\n const baseMetadata = valueFromOption(options.metadata, context);\n const timestamp = valueFromOption(options.timestamp, context);\n const baseIdempotencyKey = valueFromOption(options.idempotencyKey, context);\n const normalizedUsage = normalizeUsage(context.usage);\n\n return options.meters.flatMap((mapping) => {\n const quantity = quantityFor(mapping.quantity, context);\n if (quantity === undefined || (!mapping.includeZero && quantity === 0)) {\n return [];\n }\n\n const idempotencySuffix =\n mapping.idempotencyKeySuffix ??\n (typeof mapping.quantity === \"string\" ? mapping.quantity : eventNameFor(mapping));\n\n return [{\n name: eventNameFor(mapping),\n meter_id: mapping.meterId,\n customer_id: options.customerId,\n external_customer_id: options.externalCustomerId,\n subscription_id: options.subscriptionId,\n quantity,\n timestamp: timestamp instanceof Date ? timestamp.toISOString() : timestamp,\n idempotency_key: baseIdempotencyKey\n ? `${baseIdempotencyKey}:${idempotencySuffix}`\n : undefined,\n metadata: {\n ...(isRecord(baseMetadata) ? baseMetadata : {}),\n ...(isRecord(mapping.metadata) ? mapping.metadata : {}),\n revkeen: {\n adapter: \"@revkeen/sdk/ai\",\n schema_version: 1,\n source: \"ai_meter_adapter\",\n },\n ai: {\n provider: context.provider,\n model: context.model,\n operation: context.operation,\n usage: normalizedUsage,\n provider_hints: {\n usage_raw: context.usage.raw ?? null,\n provider_metadata: context.providerMetadata ?? null,\n },\n },\n },\n }];\n });\n}\n\nasync function emitUsage(options: RevKeenAiMeteringOptions, context: AiMeterUsageContext) {\n const events = buildAiUsageEvents(options, context);\n if (events.length === 0) return;\n\n await options.revkeen.usage.ingest(events.length === 1 ? events[0] : { events });\n}\n\nfunction resolveProvider(model: LanguageModel, fallback?: string): string {\n return fallback ?? (typeof model === \"string\" ? \"unknown\" : (model as { provider?: string }).provider ?? \"unknown\");\n}\n\nfunction resolveModel(model: LanguageModel, fallback?: string): string {\n return fallback ?? (typeof model === \"string\" ? model : (model as { modelId?: string }).modelId ?? \"unknown\");\n}\n\nfunction createMiddleware(options: RevKeenAiMeteringOptions): LanguageModelMiddleware {\n return {\n specificationVersion: \"v3\",\n wrapGenerate: async ({ doGenerate, model }) => {\n const result = await doGenerate();\n const context: AiMeterUsageContext = {\n usage: result.usage,\n provider: options.provider ?? model.provider ?? \"unknown\",\n model: options.model ?? model.modelId ?? \"unknown\",\n operation: \"generate\",\n providerMetadata: result.providerMetadata,\n };\n\n const ingest = emitUsage(options, context).catch(async (error) => {\n await options.onIngestError?.(error, context);\n if (options.awaitIngest !== false) throw error;\n });\n\n if (options.awaitIngest !== false) {\n await ingest;\n }\n\n return result;\n },\n wrapStream: async ({ doStream, model }) => {\n const result = await doStream();\n const stream = result.stream.pipeThrough(new TransformStream({\n async transform(part, controller) {\n controller.enqueue(part);\n\n if (part.type !== \"finish\") return;\n\n const context: AiMeterUsageContext = {\n usage: part.usage,\n provider: options.provider ?? model.provider ?? \"unknown\",\n model: options.model ?? model.modelId ?? \"unknown\",\n operation: \"stream\",\n providerMetadata: part.providerMetadata,\n };\n\n const ingest = emitUsage(options, context).catch(async (error) => {\n await options.onIngestError?.(error, context);\n if (options.awaitIngest !== false) throw error;\n });\n\n if (options.awaitIngest !== false) {\n await ingest;\n }\n },\n }));\n\n return {\n ...result,\n stream,\n };\n },\n };\n}\n\nexport function withRevKeenAiMetering(\n model: LanguageModel,\n options: RevKeenAiMeteringOptions\n): LanguageModel {\n assertServerSideRuntime();\n\n if (typeof model === \"string\") {\n throw new Error(\"RevKeen AI Meter Adapter requires an AI SDK language model object, not a model id string.\");\n }\n\n return wrapLanguageModel({\n model: model as Parameters<typeof wrapLanguageModel>[0][\"model\"],\n middleware: createMiddleware(options),\n providerId: resolveProvider(model, options.provider),\n modelId: resolveModel(model, options.model),\n }) as LanguageModel;\n}\n"],"mappings":"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","names":[]}