@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 +60 -0
- package/dist/ai.d.ts +60 -0
- package/dist/ai.js +227 -0
- package/dist/ai.js.map +1 -0
- package/dist/ai.mjs +202 -0
- package/dist/ai.mjs.map +1 -0
- package/dist/index.d.mts +9 -11546
- package/dist/index.d.ts +9 -11546
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +18 -0
- package/dist/index.mjs.map +1 -1
- package/dist/types.gen-kn79GXSQ.d.mts +11613 -0
- package/dist/types.gen-kn79GXSQ.d.ts +11613 -0
- package/package.json +14 -1
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
|
package/dist/ai.mjs.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":[]}
|