@x12i/ai-tools 1.0.4 → 2.0.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/CHANGELOG.md +23 -0
- package/README.md +146 -204
- package/dist/AiModelsCatalogClient-B5FMI9gj.d.cts +58 -0
- package/dist/AiModelsCatalogClient-CPPNI6Ry.d.ts +58 -0
- package/dist/aliases/index.cjs +4 -3
- package/dist/aliases/index.cjs.map +1 -1
- package/dist/aliases/index.d.cts +3 -5
- package/dist/aliases/index.d.ts +3 -5
- package/dist/aliases/index.js +2 -2
- package/dist/catalog/index.cjs +18 -9
- package/dist/catalog/index.cjs.map +1 -1
- package/dist/catalog/index.d.cts +13 -100
- package/dist/catalog/index.d.ts +13 -100
- package/dist/catalog/index.js +31 -23
- package/dist/{chunk-AJEKEWWB.js → chunk-2PTCWPHV.js} +17 -3
- package/dist/chunk-2PTCWPHV.js.map +1 -0
- package/dist/chunk-56R4XA2S.js +1 -0
- package/dist/chunk-5GUKLOEK.cjs +1 -0
- package/dist/chunk-5GUKLOEK.cjs.map +1 -0
- package/dist/chunk-5XAAMBDO.cjs +1988 -0
- package/dist/chunk-5XAAMBDO.cjs.map +1 -0
- package/dist/chunk-6BQBKROR.js +95 -0
- package/dist/chunk-6BQBKROR.js.map +1 -0
- package/dist/chunk-AB5GNXJ4.js +46 -0
- package/dist/chunk-AB5GNXJ4.js.map +1 -0
- package/dist/{chunk-O2A6OVEH.js → chunk-ANVONYJF.js} +2 -2
- package/dist/{chunk-O2A6OVEH.js.map → chunk-ANVONYJF.js.map} +1 -1
- package/dist/chunk-B3V2EHRY.js +225 -0
- package/dist/chunk-B3V2EHRY.js.map +1 -0
- package/dist/{chunk-QWAX7VQO.cjs → chunk-BAHBDADJ.cjs} +11 -11
- package/dist/{chunk-QWAX7VQO.cjs.map → chunk-BAHBDADJ.cjs.map} +1 -1
- package/dist/{chunk-TF4L2NEC.cjs → chunk-DXZOL3VN.cjs} +62 -313
- package/dist/chunk-DXZOL3VN.cjs.map +1 -0
- package/dist/chunk-EDMCKHO6.cjs +225 -0
- package/dist/chunk-EDMCKHO6.cjs.map +1 -0
- package/dist/{chunk-DJ5SWJDY.js → chunk-EYHMQVAL.js} +48 -299
- package/dist/chunk-EYHMQVAL.js.map +1 -0
- package/dist/chunk-GS7T56RP.cjs +8 -0
- package/dist/chunk-GS7T56RP.cjs.map +1 -0
- package/dist/chunk-NF2SKQR7.cjs +973 -0
- package/dist/chunk-NF2SKQR7.cjs.map +1 -0
- package/dist/chunk-OPN6BGNH.js +1985 -0
- package/dist/chunk-OPN6BGNH.js.map +1 -0
- package/dist/{chunk-7Q742NI3.cjs → chunk-PADNCGZB.cjs} +17 -3
- package/dist/chunk-PADNCGZB.cjs.map +1 -0
- package/dist/chunk-PRCICORG.cjs +95 -0
- package/dist/chunk-PRCICORG.cjs.map +1 -0
- package/dist/{chunk-6QGDZTGH.js → chunk-SIH4GPV4.js} +4 -29
- package/dist/chunk-SIH4GPV4.js.map +1 -0
- package/dist/chunk-U2YDDUVP.js +970 -0
- package/dist/chunk-U2YDDUVP.js.map +1 -0
- package/dist/{chunk-4NAY6HRP.js → chunk-VJHLO2R3.js} +7 -58
- package/dist/chunk-VJHLO2R3.js.map +1 -0
- package/dist/chunk-XAWBTX3N.cjs +46 -0
- package/dist/chunk-XAWBTX3N.cjs.map +1 -0
- package/dist/{chunk-AV6OE2YQ.cjs → chunk-XOKUDUUI.cjs} +14 -39
- package/dist/chunk-XOKUDUUI.cjs.map +1 -0
- package/dist/chunk-YQDSN6R6.cjs +86 -0
- package/dist/chunk-YQDSN6R6.cjs.map +1 -0
- package/dist/cli/index.cjs +59 -201
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +53 -198
- package/dist/cli/index.js.map +1 -1
- package/dist/cost/index.cjs +19 -3
- package/dist/cost/index.cjs.map +1 -1
- package/dist/cost/index.d.cts +10 -50
- package/dist/cost/index.d.ts +10 -50
- package/dist/cost/index.js +18 -3
- package/dist/index.cjs +24 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -13
- package/dist/index.d.ts +13 -13
- package/dist/index.js +44 -37
- package/dist/modelCache-BzRn6t_C.d.ts +113 -0
- package/dist/modelCache-CJftI-Ko.d.cts +113 -0
- package/dist/{modelNameResolver-DqFt7g6W.d.ts → modelNameResolver-5XkBMctP.d.ts} +2 -6
- package/dist/{modelNameResolver-D9V_GfUK.d.cts → modelNameResolver-C5CSTGFF.d.cts} +2 -6
- package/dist/models/index.cjs +9 -6
- package/dist/models/index.cjs.map +1 -1
- package/dist/models/index.d.cts +9 -31
- package/dist/models/index.d.ts +9 -31
- package/dist/models/index.js +9 -7
- package/dist/resolveUsageModel-BFwf80Hz.d.ts +140 -0
- package/dist/resolveUsageModel-C_YmGR1M.d.cts +140 -0
- package/dist/sync/index.cjs +7 -9
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +3 -8
- package/dist/sync/index.d.ts +3 -8
- package/dist/sync/index.js +8 -11
- package/dist/toolbox/index.cjs +1 -0
- package/dist/toolbox/index.cjs.map +1 -1
- package/dist/types-BrzJWsTU.d.cts +277 -0
- package/dist/types-BrzJWsTU.d.ts +277 -0
- package/package.json +9 -20
- package/src/data/models-catalog.json +670 -0
- package/src/data/openrouter-models-catalog.json +857 -0
- package/dist/AiModelsCatalogClient-4RF5BCDL.cjs +0 -9
- package/dist/AiModelsCatalogClient-4RF5BCDL.cjs.map +0 -1
- package/dist/AiModelsCatalogClient-CNeqFiFs.d.cts +0 -30
- package/dist/AiModelsCatalogClient-NUF3CBLW.js +0 -9
- package/dist/AiModelsCatalogClient-nwFoEaqL.d.ts +0 -30
- package/dist/catalox/index.cjs +0 -21
- package/dist/catalox/index.cjs.map +0 -1
- package/dist/catalox/index.d.cts +0 -11
- package/dist/catalox/index.d.ts +0 -11
- package/dist/catalox/index.js +0 -21
- package/dist/catalox/index.js.map +0 -1
- package/dist/chunk-4NAY6HRP.js.map +0 -1
- package/dist/chunk-6QGDZTGH.js.map +0 -1
- package/dist/chunk-7Q742NI3.cjs.map +0 -1
- package/dist/chunk-AJEKEWWB.js.map +0 -1
- package/dist/chunk-AV6OE2YQ.cjs.map +0 -1
- package/dist/chunk-C3H7RTFR.cjs +0 -1
- package/dist/chunk-C3H7RTFR.cjs.map +0 -1
- package/dist/chunk-DJ5SWJDY.js.map +0 -1
- package/dist/chunk-DKHGWHXP.cjs +0 -169
- package/dist/chunk-DKHGWHXP.cjs.map +0 -1
- package/dist/chunk-F2F4UEFD.cjs +0 -75
- package/dist/chunk-F2F4UEFD.cjs.map +0 -1
- package/dist/chunk-FGP3QXWL.cjs +0 -163
- package/dist/chunk-FGP3QXWL.cjs.map +0 -1
- package/dist/chunk-G2G4KSC5.js +0 -30
- package/dist/chunk-G2G4KSC5.js.map +0 -1
- package/dist/chunk-HN6UAQAE.cjs +0 -83
- package/dist/chunk-HN6UAQAE.cjs.map +0 -1
- package/dist/chunk-HS74X2OJ.cjs +0 -172
- package/dist/chunk-HS74X2OJ.cjs.map +0 -1
- package/dist/chunk-HYGXZY25.js +0 -163
- package/dist/chunk-HYGXZY25.js.map +0 -1
- package/dist/chunk-KQOALKKX.js +0 -75
- package/dist/chunk-KQOALKKX.js.map +0 -1
- package/dist/chunk-LYOU7CA2.cjs +0 -30
- package/dist/chunk-LYOU7CA2.cjs.map +0 -1
- package/dist/chunk-M5TMA73F.js +0 -1
- package/dist/chunk-M5TMA73F.js.map +0 -1
- package/dist/chunk-MX3AMQFC.js +0 -172
- package/dist/chunk-MX3AMQFC.js.map +0 -1
- package/dist/chunk-QCRLKVB3.cjs +0 -137
- package/dist/chunk-QCRLKVB3.cjs.map +0 -1
- package/dist/chunk-TF4L2NEC.cjs.map +0 -1
- package/dist/chunk-VRFVF5RH.js +0 -169
- package/dist/chunk-VRFVF5RH.js.map +0 -1
- package/dist/chunk-YHO57D2V.js +0 -83
- package/dist/chunk-YHO57D2V.js.map +0 -1
- package/dist/syncAiModelsCatalog-CnXRLm2c.d.cts +0 -32
- package/dist/syncAiModelsCatalog-DpkN_w7S.d.ts +0 -32
- package/dist/types-BYXnCvKx.d.cts +0 -137
- package/dist/types-BYXnCvKx.d.ts +0 -137
- package/dist/types-CX6QFNNy.d.cts +0 -144
- package/dist/types-CuiPDcVs.d.ts +0 -144
- package/dist/upsertAiModelRecord-C831wOIF.d.ts +0 -35
- package/dist/upsertAiModelRecord-CjY-sny0.d.cts +0 -35
- /package/dist/{AiModelsCatalogClient-NUF3CBLW.js.map → chunk-56R4XA2S.js.map} +0 -0
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LOCAL_PROVIDERS,
|
|
3
|
+
loadOpenRouterRoutingEnv,
|
|
4
|
+
normalizeProvider,
|
|
5
|
+
normalizeString,
|
|
6
|
+
shouldDefaultRouteViaOpenRouter,
|
|
7
|
+
stripModelVersionSuffix
|
|
8
|
+
} from "./chunk-B3V2EHRY.js";
|
|
9
|
+
import {
|
|
10
|
+
UnknownModelCostError
|
|
11
|
+
} from "./chunk-2PTCWPHV.js";
|
|
12
|
+
|
|
13
|
+
// src/cost/constants.ts
|
|
14
|
+
var DEFAULT_OPENROUTER_MARKUP_RATE = 0.05;
|
|
15
|
+
var PROFILE_TIERED_CONTEXT_THRESHOLD = 2e5;
|
|
16
|
+
|
|
17
|
+
// src/cost/enrichCostResult.ts
|
|
18
|
+
function toCostExtraction(provenance) {
|
|
19
|
+
return {
|
|
20
|
+
model: { field: provenance.model.field, path: provenance.model.path },
|
|
21
|
+
tokens: {
|
|
22
|
+
source: provenance.tokens.source,
|
|
23
|
+
path: provenance.tokens.path,
|
|
24
|
+
estimated: provenance.tokens.estimated
|
|
25
|
+
},
|
|
26
|
+
...provenance.modelUsed ? {
|
|
27
|
+
modelUsed: {
|
|
28
|
+
field: provenance.modelUsed.field,
|
|
29
|
+
path: provenance.modelUsed.path
|
|
30
|
+
}
|
|
31
|
+
} : {},
|
|
32
|
+
...provenance.provider ? {
|
|
33
|
+
provider: {
|
|
34
|
+
field: provenance.provider.field,
|
|
35
|
+
path: provenance.provider.path
|
|
36
|
+
}
|
|
37
|
+
} : {}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function enrichCostResult(result, input, extraction) {
|
|
41
|
+
const receivedModelId = input.usedModel ?? input.modelUsed ?? input.model;
|
|
42
|
+
return {
|
|
43
|
+
...result,
|
|
44
|
+
provider: input.provider,
|
|
45
|
+
usage: { ...input.tokens },
|
|
46
|
+
...receivedModelId ? { usedModel: receivedModelId } : {},
|
|
47
|
+
...input.model && input.model !== receivedModelId ? { model: input.model } : {},
|
|
48
|
+
...extraction ? { extraction } : {}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/cost/extractUsageInput.ts
|
|
53
|
+
var MODEL_FIELD_PRIORITY = [
|
|
54
|
+
{ key: "usedModel", score: 1e3 },
|
|
55
|
+
{ key: "modelUsed", score: 990 },
|
|
56
|
+
{ key: "resolvedModel", score: 980 },
|
|
57
|
+
{ key: "model", score: 500 },
|
|
58
|
+
{ key: "xynthesisModel", score: 120 },
|
|
59
|
+
{ key: "skillModel", score: 110 }
|
|
60
|
+
];
|
|
61
|
+
var MODEL_PATH_BONUS = [
|
|
62
|
+
{ pattern: /(^|\.)response(\.|$)/i, bonus: 40 },
|
|
63
|
+
{ pattern: /(^|\.)outer\.input(\.|$)/i, bonus: 35 },
|
|
64
|
+
{ pattern: /(^|\.)config(?!\.rawConfig)(\.|$)/i, bonus: 30 },
|
|
65
|
+
{ pattern: /(^|\.)outer\.input\.config(\.|$)/i, bonus: 28 },
|
|
66
|
+
{ pattern: /(^|\.)usage(\.|$)/i, bonus: 15 },
|
|
67
|
+
{ pattern: /(^|\.)rawConfig(\.|$)/i, bonus: -40 },
|
|
68
|
+
{ pattern: /workingMemory/i, bonus: -50 },
|
|
69
|
+
{ pattern: /requestExample/i, bonus: -80 },
|
|
70
|
+
{ pattern: /graphsStudio/i, bonus: -80 }
|
|
71
|
+
];
|
|
72
|
+
var PROVIDER_FIELD_PRIORITY = [
|
|
73
|
+
{ key: "provider", score: 500 },
|
|
74
|
+
{ key: "providerId", score: 480 }
|
|
75
|
+
];
|
|
76
|
+
var PROVIDER_PATH_BONUS = [
|
|
77
|
+
{ pattern: /(^|\.)config(?!\.rawConfig)(\.|$)/i, bonus: 30 },
|
|
78
|
+
{ pattern: /(^|\.)outer\.input(\.|$)/i, bonus: 20 },
|
|
79
|
+
{ pattern: /(^|\.)rawConfig(\.|$)/i, bonus: -20 }
|
|
80
|
+
];
|
|
81
|
+
var PROMPT_TOKEN_KEYS = /* @__PURE__ */ new Set([
|
|
82
|
+
"prompt",
|
|
83
|
+
"prompt_tokens",
|
|
84
|
+
"promptTokens",
|
|
85
|
+
"input_tokens",
|
|
86
|
+
"inputTokens",
|
|
87
|
+
"estimatedInputTokens",
|
|
88
|
+
"input"
|
|
89
|
+
]);
|
|
90
|
+
var COMPLETION_TOKEN_KEYS = /* @__PURE__ */ new Set([
|
|
91
|
+
"completion",
|
|
92
|
+
"completion_tokens",
|
|
93
|
+
"completionTokens",
|
|
94
|
+
"output_tokens",
|
|
95
|
+
"outputTokens",
|
|
96
|
+
"estimatedOutputTokens",
|
|
97
|
+
"output"
|
|
98
|
+
]);
|
|
99
|
+
var OPTIONAL_TOKEN_KEYS = {
|
|
100
|
+
total: "total",
|
|
101
|
+
total_tokens: "total",
|
|
102
|
+
totalTokens: "total",
|
|
103
|
+
cached: "cached",
|
|
104
|
+
cached_tokens: "cached",
|
|
105
|
+
cache_read_tokens: "cached",
|
|
106
|
+
cacheReadTokens: "cached",
|
|
107
|
+
cacheWrite: "cacheWrite",
|
|
108
|
+
cache_write_tokens: "cacheWrite",
|
|
109
|
+
cacheWriteTokens: "cacheWrite",
|
|
110
|
+
reasoning: "reasoning",
|
|
111
|
+
reasoning_tokens: "reasoning",
|
|
112
|
+
reasoningTokens: "reasoning",
|
|
113
|
+
audio: "audio",
|
|
114
|
+
audio_tokens: "audio",
|
|
115
|
+
image: "image",
|
|
116
|
+
image_tokens: "image"
|
|
117
|
+
};
|
|
118
|
+
function isPlainObject(v) {
|
|
119
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
120
|
+
}
|
|
121
|
+
function modelKeyScore(key) {
|
|
122
|
+
return MODEL_FIELD_PRIORITY.find((e) => e.key === key)?.score ?? 0;
|
|
123
|
+
}
|
|
124
|
+
function providerKeyScore(key) {
|
|
125
|
+
return PROVIDER_FIELD_PRIORITY.find((e) => e.key === key)?.score ?? 0;
|
|
126
|
+
}
|
|
127
|
+
function pathBonus(path, rules) {
|
|
128
|
+
let bonus = 0;
|
|
129
|
+
for (const { pattern, bonus: b } of rules) {
|
|
130
|
+
if (pattern.test(path)) bonus += b;
|
|
131
|
+
}
|
|
132
|
+
return bonus;
|
|
133
|
+
}
|
|
134
|
+
function normalizeModelValue(v) {
|
|
135
|
+
if (typeof v !== "string") return void 0;
|
|
136
|
+
const t = v.trim();
|
|
137
|
+
return t.length > 0 ? t : void 0;
|
|
138
|
+
}
|
|
139
|
+
function normalizeProviderValue(v) {
|
|
140
|
+
if (typeof v !== "string") return void 0;
|
|
141
|
+
const t = v.trim().toLowerCase();
|
|
142
|
+
if (!t || t === "null") return void 0;
|
|
143
|
+
return t;
|
|
144
|
+
}
|
|
145
|
+
function collectModelCandidates(node, path, out, depth, maxDepth) {
|
|
146
|
+
if (depth > maxDepth || node === null || node === void 0) return;
|
|
147
|
+
if (isPlainObject(node)) {
|
|
148
|
+
for (const [key, value] of Object.entries(node)) {
|
|
149
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
150
|
+
const base = modelKeyScore(key);
|
|
151
|
+
if (base > 0) {
|
|
152
|
+
const model = normalizeModelValue(value);
|
|
153
|
+
if (model) {
|
|
154
|
+
out.push({
|
|
155
|
+
field: key,
|
|
156
|
+
path: childPath,
|
|
157
|
+
value: model,
|
|
158
|
+
score: base + pathBonus(childPath, MODEL_PATH_BONUS)
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
collectModelCandidates(value, childPath, out, depth + 1, maxDepth);
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (Array.isArray(node)) {
|
|
167
|
+
for (let i = 0; i < node.length; i++) {
|
|
168
|
+
collectModelCandidates(node[i], `${path}[${i}]`, out, depth + 1, maxDepth);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function collectProviderCandidates(node, path, out, depth, maxDepth) {
|
|
173
|
+
if (depth > maxDepth || node === null || node === void 0) return;
|
|
174
|
+
if (isPlainObject(node)) {
|
|
175
|
+
for (const [key, value] of Object.entries(node)) {
|
|
176
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
177
|
+
const base = providerKeyScore(key);
|
|
178
|
+
if (base > 0) {
|
|
179
|
+
const provider = normalizeProviderValue(value);
|
|
180
|
+
if (provider) {
|
|
181
|
+
out.push({
|
|
182
|
+
field: key,
|
|
183
|
+
path: childPath,
|
|
184
|
+
value: provider,
|
|
185
|
+
score: base + pathBonus(childPath, PROVIDER_PATH_BONUS)
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
collectProviderCandidates(value, childPath, out, depth + 1, maxDepth);
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (Array.isArray(node)) {
|
|
194
|
+
for (let i = 0; i < node.length; i++) {
|
|
195
|
+
collectProviderCandidates(node[i], `${path}[${i}]`, out, depth + 1, maxDepth);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function readNumber(obj, keys) {
|
|
200
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
201
|
+
if (!keys.has(k)) continue;
|
|
202
|
+
if (typeof v === "number" && Number.isFinite(v) && v >= 0) return v;
|
|
203
|
+
}
|
|
204
|
+
return void 0;
|
|
205
|
+
}
|
|
206
|
+
function readOptionalNumbers(obj) {
|
|
207
|
+
const extra = {};
|
|
208
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
209
|
+
const target = OPTIONAL_TOKEN_KEYS[k];
|
|
210
|
+
if (!target || typeof v !== "number" || !Number.isFinite(v) || v < 0) continue;
|
|
211
|
+
extra[target] = v;
|
|
212
|
+
}
|
|
213
|
+
return extra;
|
|
214
|
+
}
|
|
215
|
+
function looksLikeUsageObject(obj) {
|
|
216
|
+
const hasPrompt = readNumber(obj, PROMPT_TOKEN_KEYS) !== void 0;
|
|
217
|
+
const hasCompletion = readNumber(obj, COMPLETION_TOKEN_KEYS) !== void 0;
|
|
218
|
+
return hasPrompt && hasCompletion;
|
|
219
|
+
}
|
|
220
|
+
function looksLikeDiagnosticsObject(obj) {
|
|
221
|
+
return typeof obj.estimatedInputTokens === "number" && typeof obj.estimatedOutputTokens === "number";
|
|
222
|
+
}
|
|
223
|
+
function tokensFromObject(obj, source, path, estimated, score) {
|
|
224
|
+
const prompt = readNumber(obj, PROMPT_TOKEN_KEYS);
|
|
225
|
+
const completion = readNumber(obj, COMPLETION_TOKEN_KEYS);
|
|
226
|
+
if (prompt === void 0 || completion === void 0) return null;
|
|
227
|
+
const extra = readOptionalNumbers(obj);
|
|
228
|
+
const total = typeof extra.total === "number" ? extra.total : prompt + completion;
|
|
229
|
+
return {
|
|
230
|
+
tokens: {
|
|
231
|
+
prompt,
|
|
232
|
+
completion,
|
|
233
|
+
total,
|
|
234
|
+
...extra
|
|
235
|
+
},
|
|
236
|
+
provenance: { source, path, estimated },
|
|
237
|
+
score
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function collectTokenCandidates(node, path, out, depth, maxDepth) {
|
|
241
|
+
if (depth > maxDepth || node === null || node === void 0) return;
|
|
242
|
+
if (isPlainObject(node)) {
|
|
243
|
+
if (path.endsWith("diagnostics") || looksLikeDiagnosticsObject(node)) {
|
|
244
|
+
const cand = tokensFromObject(node, "diagnostics", path, true, 200);
|
|
245
|
+
if (cand) out.push(cand);
|
|
246
|
+
} else if (path.endsWith("usage") || path.endsWith("tokens") || looksLikeUsageObject(node)) {
|
|
247
|
+
const source = path.endsWith("tokens") ? "tokens" : "usage";
|
|
248
|
+
const estimated = false;
|
|
249
|
+
const score = path.endsWith("usage") ? 500 : 450;
|
|
250
|
+
const cand = tokensFromObject(node, source, path, estimated, score);
|
|
251
|
+
if (cand) out.push(cand);
|
|
252
|
+
}
|
|
253
|
+
for (const [key, value] of Object.entries(node)) {
|
|
254
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
255
|
+
if (key === "usage" || key === "tokens" || key === "diagnostics") {
|
|
256
|
+
if (isPlainObject(value)) {
|
|
257
|
+
collectTokenCandidates(value, childPath, out, depth + 1, maxDepth);
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
collectTokenCandidates(value, childPath, out, depth + 1, maxDepth);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (Array.isArray(node)) {
|
|
266
|
+
for (let i = 0; i < node.length; i++) {
|
|
267
|
+
collectTokenCandidates(node[i], `${path}[${i}]`, out, depth + 1, maxDepth);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function pickBestModel(candidates) {
|
|
272
|
+
if (candidates.length === 0) return null;
|
|
273
|
+
return [...candidates].sort((a, b) => b.score - a.score)[0];
|
|
274
|
+
}
|
|
275
|
+
function pickBestProvider(candidates) {
|
|
276
|
+
if (candidates.length === 0) return null;
|
|
277
|
+
return [...candidates].sort((a, b) => b.score - a.score)[0];
|
|
278
|
+
}
|
|
279
|
+
function pickBestTokens(candidates) {
|
|
280
|
+
if (candidates.length === 0) return null;
|
|
281
|
+
return [...candidates].sort((a, b) => b.score - a.score)[0];
|
|
282
|
+
}
|
|
283
|
+
function inferProviderFromModel(model, fallback) {
|
|
284
|
+
const slash = model.indexOf("/");
|
|
285
|
+
if (slash > 0) {
|
|
286
|
+
const prefix = model.slice(0, slash).toLowerCase();
|
|
287
|
+
if (prefix !== "openrouter") return prefix;
|
|
288
|
+
}
|
|
289
|
+
return fallback;
|
|
290
|
+
}
|
|
291
|
+
function extractUsageInput(record, options = {}) {
|
|
292
|
+
const defaultProvider = options.defaultProvider ?? "openrouter";
|
|
293
|
+
const maxDepth = 24;
|
|
294
|
+
const modelCandidates = [];
|
|
295
|
+
const providerCandidates = [];
|
|
296
|
+
const tokenCandidates = [];
|
|
297
|
+
collectModelCandidates(record, "", modelCandidates, 0, maxDepth);
|
|
298
|
+
collectProviderCandidates(record, "", providerCandidates, 0, maxDepth);
|
|
299
|
+
collectTokenCandidates(record, "", tokenCandidates, 0, maxDepth);
|
|
300
|
+
const bestModel = pickBestModel(modelCandidates);
|
|
301
|
+
if (!bestModel) {
|
|
302
|
+
throw new Error(
|
|
303
|
+
"[ai-tools] Could not extract a model from the record (looked for usedModel, modelUsed, model, \u2026)."
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
const runtimeFields = /* @__PURE__ */ new Set(["usedModel", "modelUsed", "resolvedModel"]);
|
|
307
|
+
const isRuntimeModel = runtimeFields.has(bestModel.field);
|
|
308
|
+
const requestModel = modelCandidates.find(
|
|
309
|
+
(c) => c.field === "model" && c.path !== bestModel.path
|
|
310
|
+
);
|
|
311
|
+
const bestProvider = pickBestProvider(providerCandidates);
|
|
312
|
+
const provider = bestProvider?.value ?? inferProviderFromModel(bestModel.value, defaultProvider);
|
|
313
|
+
const bestTokens = pickBestTokens(tokenCandidates);
|
|
314
|
+
if (!bestTokens) {
|
|
315
|
+
throw new Error(
|
|
316
|
+
"[ai-tools] Could not extract token usage from the record (usage, tokens, or metadata.diagnostics)."
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
const input = {
|
|
320
|
+
tokens: bestTokens.tokens,
|
|
321
|
+
provider,
|
|
322
|
+
...isRuntimeModel ? { modelUsed: bestModel.value, model: requestModel?.value } : { model: bestModel.value }
|
|
323
|
+
};
|
|
324
|
+
const provenance = {
|
|
325
|
+
model: {
|
|
326
|
+
field: bestModel.field,
|
|
327
|
+
path: bestModel.path,
|
|
328
|
+
score: bestModel.score
|
|
329
|
+
},
|
|
330
|
+
tokens: bestTokens.provenance
|
|
331
|
+
};
|
|
332
|
+
if (bestProvider) {
|
|
333
|
+
provenance.provider = {
|
|
334
|
+
field: bestProvider.field,
|
|
335
|
+
path: bestProvider.path,
|
|
336
|
+
score: bestProvider.score
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (isRuntimeModel && requestModel) {
|
|
340
|
+
provenance.modelUsed = {
|
|
341
|
+
field: bestModel.field,
|
|
342
|
+
path: bestModel.path,
|
|
343
|
+
score: bestModel.score
|
|
344
|
+
};
|
|
345
|
+
} else if (input.modelUsed) {
|
|
346
|
+
provenance.modelUsed = provenance.model;
|
|
347
|
+
}
|
|
348
|
+
return { input, provenance };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/cost/resolveUsageModel.ts
|
|
352
|
+
function resolveUsageModel(input) {
|
|
353
|
+
const value = input.usedModel ?? input.modelUsed ?? input.model;
|
|
354
|
+
if (!value?.trim()) {
|
|
355
|
+
throw new Error(
|
|
356
|
+
"[ai-tools] Cost calculation requires `usedModel`, `modelUsed`, or `model` on usage input."
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
return value.trim();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/cost/aiProfilesMatch.ts
|
|
363
|
+
import { createRequire } from "module";
|
|
364
|
+
import { dirname, join } from "path";
|
|
365
|
+
import { AIProfilesError, resolveAIProfile } from "@x12i/ai-profiles";
|
|
366
|
+
|
|
367
|
+
// src/cost/costModelResolution.ts
|
|
368
|
+
function isOpenRouterProvider(provider) {
|
|
369
|
+
return normalizeProvider(provider) === "openrouter";
|
|
370
|
+
}
|
|
371
|
+
function isLocalProvider(provider) {
|
|
372
|
+
const p = normalizeProvider(provider) ?? normalizeString(provider ?? "");
|
|
373
|
+
return LOCAL_PROVIDERS.has(p);
|
|
374
|
+
}
|
|
375
|
+
function isLocalProviderResolution(resolved, inputProvider) {
|
|
376
|
+
if (!resolved?.found || resolved.record) return false;
|
|
377
|
+
if (resolved.resolvedVia.includes("local-provider-passthrough")) return true;
|
|
378
|
+
return isLocalProvider(inputProvider);
|
|
379
|
+
}
|
|
380
|
+
function underlyingProviderFromModel(model, providerHint) {
|
|
381
|
+
const normalized = normalizeString(model);
|
|
382
|
+
if (normalized.includes("/")) {
|
|
383
|
+
const prefix = normalized.split("/")[0];
|
|
384
|
+
if (!isOpenRouterProvider(prefix)) return prefix;
|
|
385
|
+
}
|
|
386
|
+
if (providerHint && !isOpenRouterProvider(providerHint)) {
|
|
387
|
+
return normalizeProvider(providerHint);
|
|
388
|
+
}
|
|
389
|
+
return void 0;
|
|
390
|
+
}
|
|
391
|
+
function providerHintForProfiles(model, provider) {
|
|
392
|
+
if (isOpenRouterProvider(provider)) {
|
|
393
|
+
return underlyingProviderFromModel(model, provider);
|
|
394
|
+
}
|
|
395
|
+
return normalizeProvider(provider);
|
|
396
|
+
}
|
|
397
|
+
function buildCatalogResolveAttempts(model, provider) {
|
|
398
|
+
const attempts = [];
|
|
399
|
+
const seen = /* @__PURE__ */ new Set();
|
|
400
|
+
const add = (m, p) => {
|
|
401
|
+
const key = `${p ?? ""}\0${normalizeString(m)}`;
|
|
402
|
+
if (seen.has(key)) return;
|
|
403
|
+
seen.add(key);
|
|
404
|
+
attempts.push({ model: m, provider: p });
|
|
405
|
+
};
|
|
406
|
+
add(model, provider);
|
|
407
|
+
const underlying = underlyingProviderFromModel(model, provider);
|
|
408
|
+
if (provider) add(model, void 0);
|
|
409
|
+
if (underlying) add(model, underlying);
|
|
410
|
+
if (!isOpenRouterProvider(provider)) {
|
|
411
|
+
add(model, "openrouter");
|
|
412
|
+
}
|
|
413
|
+
return attempts;
|
|
414
|
+
}
|
|
415
|
+
async function resolveFromCatalogAttempts(catalog, attempts, options) {
|
|
416
|
+
let last = null;
|
|
417
|
+
let localPassthrough = null;
|
|
418
|
+
for (const attempt of attempts) {
|
|
419
|
+
const result = await catalog.resolveModel(attempt, options);
|
|
420
|
+
last = result;
|
|
421
|
+
if (result.found && result.record) return result;
|
|
422
|
+
if (result.found && !result.record && isLocalProviderResolution(result, attempt.provider)) {
|
|
423
|
+
localPassthrough = result;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return localPassthrough ?? last;
|
|
427
|
+
}
|
|
428
|
+
function resolveRoutedViaOpenRouter(inputProvider, resolved, modelId, routingEnv) {
|
|
429
|
+
const env = routingEnv ?? loadOpenRouterRoutingEnv();
|
|
430
|
+
if (isOpenRouterProvider(inputProvider)) return true;
|
|
431
|
+
if (resolved?.found) {
|
|
432
|
+
return resolved.routedViaOpenRouter;
|
|
433
|
+
}
|
|
434
|
+
const vendor = underlyingProviderFromModel(
|
|
435
|
+
modelId ?? (resolved?.found ? resolved.modelId : "") ?? "",
|
|
436
|
+
inputProvider
|
|
437
|
+
);
|
|
438
|
+
if (vendor && shouldDefaultRouteViaOpenRouter(vendor, env)) {
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/cost/aiProfilesMatch.ts
|
|
445
|
+
var require2 = createRequire(import.meta.url);
|
|
446
|
+
function loadProfilesRegistry() {
|
|
447
|
+
const entry = dirname(require2.resolve("@x12i/ai-profiles"));
|
|
448
|
+
const loader = require2(join(entry, "loader/loadAIProfilesRegistry.js"));
|
|
449
|
+
return loader.loadAIProfilesRegistry({ source: "auto" });
|
|
450
|
+
}
|
|
451
|
+
function profileKey(value) {
|
|
452
|
+
return value.trim().toLowerCase();
|
|
453
|
+
}
|
|
454
|
+
function canonicalModelId(provider, modelId) {
|
|
455
|
+
const normalized = normalizeString(modelId);
|
|
456
|
+
if (normalized.includes("/")) return normalized;
|
|
457
|
+
const p = normalizeProvider(provider) ?? normalizeString(provider);
|
|
458
|
+
return `${p}/${normalized}`;
|
|
459
|
+
}
|
|
460
|
+
var cachedIndex = null;
|
|
461
|
+
function indexRegistry(registry) {
|
|
462
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
463
|
+
const add = (key, entry) => {
|
|
464
|
+
const k = profileKey(key);
|
|
465
|
+
if (!k) return;
|
|
466
|
+
const list = byKey.get(k) ?? [];
|
|
467
|
+
if (list.some(
|
|
468
|
+
(e) => e.canonicalModelId === entry.canonicalModelId && e.choice === entry.choice && e.profile === entry.profile
|
|
469
|
+
)) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
list.push(entry);
|
|
473
|
+
byKey.set(k, list);
|
|
474
|
+
};
|
|
475
|
+
for (const shortcutKey of Object.keys(registry.shortcuts)) {
|
|
476
|
+
const shortcut = registry.shortcuts[shortcutKey];
|
|
477
|
+
const profile = registry.profiles[shortcut.profile];
|
|
478
|
+
if (!profile) continue;
|
|
479
|
+
const choiceKey = shortcut.choice ?? profile.defaultChoice;
|
|
480
|
+
const choice = profile.choices[choiceKey];
|
|
481
|
+
if (!choice) continue;
|
|
482
|
+
const entry = choiceEntry(choice, profile.profile, choiceKey, "shortcut");
|
|
483
|
+
add(shortcutKey, entry);
|
|
484
|
+
}
|
|
485
|
+
for (const profile of Object.values(registry.profiles)) {
|
|
486
|
+
for (const [choiceKey, choice] of Object.entries(profile.choices)) {
|
|
487
|
+
const entry = choiceEntry(choice, profile.profile, choiceKey, "model-id");
|
|
488
|
+
add(choice.modelId, entry);
|
|
489
|
+
add(canonicalModelId(choice.provider, choice.modelId), entry);
|
|
490
|
+
}
|
|
491
|
+
const defaultChoice = profile.choices[profile.defaultChoice];
|
|
492
|
+
if (defaultChoice) {
|
|
493
|
+
add(
|
|
494
|
+
profile.profile,
|
|
495
|
+
choiceEntry(defaultChoice, profile.profile, profile.defaultChoice, "profile")
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
for (const alias of profile.aliases ?? []) {
|
|
499
|
+
if (defaultChoice) {
|
|
500
|
+
add(
|
|
501
|
+
alias,
|
|
502
|
+
choiceEntry(
|
|
503
|
+
defaultChoice,
|
|
504
|
+
profile.profile,
|
|
505
|
+
profile.defaultChoice,
|
|
506
|
+
"profile-alias"
|
|
507
|
+
)
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return byKey;
|
|
513
|
+
}
|
|
514
|
+
function choiceEntry(choice, profile, choiceKey, matchedVia) {
|
|
515
|
+
const canonical = canonicalModelId(choice.provider, choice.modelId);
|
|
516
|
+
return {
|
|
517
|
+
provider: normalizeProvider(choice.provider) ?? choice.provider,
|
|
518
|
+
modelId: normalizeString(choice.modelId),
|
|
519
|
+
canonicalModelId: canonical,
|
|
520
|
+
pricing: choice.pricing,
|
|
521
|
+
matchedVia,
|
|
522
|
+
profile,
|
|
523
|
+
choice: choiceKey
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
async function getProfileIndex() {
|
|
527
|
+
const registry = await loadProfilesRegistry();
|
|
528
|
+
const cacheKey = `${registry.version}:${registry.generatedAt ?? ""}:${registry.source}`;
|
|
529
|
+
if (cachedIndex?.cacheKey === cacheKey) {
|
|
530
|
+
return cachedIndex.byKey;
|
|
531
|
+
}
|
|
532
|
+
const byKey = indexRegistry(registry);
|
|
533
|
+
cachedIndex = { cacheKey, byKey };
|
|
534
|
+
return byKey;
|
|
535
|
+
}
|
|
536
|
+
function scoreCandidate(entry, model, providerHint) {
|
|
537
|
+
const normalized = normalizeString(model);
|
|
538
|
+
let score = 0;
|
|
539
|
+
if (normalized === entry.canonicalModelId) score += 100;
|
|
540
|
+
if (normalized === entry.modelId) score += 80;
|
|
541
|
+
if (providerHint && entry.provider === providerHint) score += 50;
|
|
542
|
+
if (entry.matchedVia === "model-id") score += 10;
|
|
543
|
+
if (entry.matchedVia === "shortcut") score += 5;
|
|
544
|
+
if (entry.matchedVia === "profile") score += 3;
|
|
545
|
+
if (entry.pricing) score += 2;
|
|
546
|
+
return score;
|
|
547
|
+
}
|
|
548
|
+
function pickBest(candidates, model, providerHint) {
|
|
549
|
+
if (candidates.length === 0) return null;
|
|
550
|
+
if (candidates.length === 1) return candidates[0];
|
|
551
|
+
let best = candidates[0];
|
|
552
|
+
let bestScore = scoreCandidate(best, model, providerHint);
|
|
553
|
+
for (let i = 1; i < candidates.length; i++) {
|
|
554
|
+
const c = candidates[i];
|
|
555
|
+
const s = scoreCandidate(c, model, providerHint);
|
|
556
|
+
if (s > bestScore) {
|
|
557
|
+
best = c;
|
|
558
|
+
bestScore = s;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return best;
|
|
562
|
+
}
|
|
563
|
+
function fromIndexed(entry) {
|
|
564
|
+
return {
|
|
565
|
+
provider: entry.provider,
|
|
566
|
+
modelId: entry.modelId,
|
|
567
|
+
canonicalModelId: entry.canonicalModelId,
|
|
568
|
+
pricing: entry.pricing,
|
|
569
|
+
matchedVia: entry.matchedVia,
|
|
570
|
+
profile: entry.profile,
|
|
571
|
+
choice: entry.choice
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function lookupInIndex(index, model, provider) {
|
|
575
|
+
const providerHint = provider ? normalizeProvider(provider) : void 0;
|
|
576
|
+
const keys = [
|
|
577
|
+
profileKey(model),
|
|
578
|
+
profileKey(normalizeString(model))
|
|
579
|
+
];
|
|
580
|
+
if (providerHint) {
|
|
581
|
+
keys.push(profileKey(canonicalModelId(providerHint, model)));
|
|
582
|
+
}
|
|
583
|
+
const pooled = [];
|
|
584
|
+
for (const key of keys) {
|
|
585
|
+
const hits = index.get(key);
|
|
586
|
+
if (hits) pooled.push(...hits);
|
|
587
|
+
}
|
|
588
|
+
const bare = normalizeString(model);
|
|
589
|
+
if (!bare.includes("/")) {
|
|
590
|
+
for (const list of index.values()) {
|
|
591
|
+
for (const entry of list) {
|
|
592
|
+
if (entry.modelId === bare) pooled.push(entry);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
const unique = /* @__PURE__ */ new Map();
|
|
597
|
+
for (const e of pooled) {
|
|
598
|
+
unique.set(`${e.profile}:${e.choice}:${e.canonicalModelId}`, e);
|
|
599
|
+
}
|
|
600
|
+
const best = pickBest([...unique.values()], model, providerHint);
|
|
601
|
+
return best ? fromIndexed(best) : null;
|
|
602
|
+
}
|
|
603
|
+
async function matchModelInAiProfiles(model, provider) {
|
|
604
|
+
try {
|
|
605
|
+
const resolved = await resolveAIProfile(model);
|
|
606
|
+
return {
|
|
607
|
+
provider: normalizeProvider(resolved.provider) ?? resolved.provider,
|
|
608
|
+
modelId: normalizeString(resolved.modelId),
|
|
609
|
+
canonicalModelId: canonicalModelId(resolved.provider, resolved.modelId),
|
|
610
|
+
pricing: resolved.pricing,
|
|
611
|
+
matchedVia: resolved.shortcut ? "shortcut" : "profile",
|
|
612
|
+
profile: resolved.profile,
|
|
613
|
+
choice: resolved.choice
|
|
614
|
+
};
|
|
615
|
+
} catch (err) {
|
|
616
|
+
if (!(err instanceof AIProfilesError) || err.code !== "UNKNOWN_PROFILE") {
|
|
617
|
+
throw err;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const index = await getProfileIndex();
|
|
621
|
+
const hint = providerHintForProfiles(model, provider);
|
|
622
|
+
const direct = lookupInIndex(index, model, hint);
|
|
623
|
+
if (direct) return direct;
|
|
624
|
+
const stripped = stripModelVersionSuffix(model);
|
|
625
|
+
if (stripped && stripped !== normalizeString(model)) {
|
|
626
|
+
return lookupInIndex(index, stripped, providerHintForProfiles(stripped, provider));
|
|
627
|
+
}
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// src/cost/profilePricing.ts
|
|
632
|
+
var TOKENS_PER_UNIT = 1e6;
|
|
633
|
+
function usdPerToken(perMillion) {
|
|
634
|
+
return (perMillion ?? 0) / TOKENS_PER_UNIT;
|
|
635
|
+
}
|
|
636
|
+
function pickInputRatePerMillion(pricing, promptTokens) {
|
|
637
|
+
if (pricing.inputBelow200k !== void 0 && pricing.inputAbove200k !== void 0) {
|
|
638
|
+
return promptTokens <= PROFILE_TIERED_CONTEXT_THRESHOLD ? pricing.inputBelow200k : pricing.inputAbove200k;
|
|
639
|
+
}
|
|
640
|
+
return pricing.input ?? pricing.inputBelow200k ?? pricing.inputAbove200k;
|
|
641
|
+
}
|
|
642
|
+
function pickOutputRatePerMillion(pricing, promptTokens) {
|
|
643
|
+
if (pricing.outputBelow200k !== void 0 && pricing.outputAbove200k !== void 0) {
|
|
644
|
+
return promptTokens <= PROFILE_TIERED_CONTEXT_THRESHOLD ? pricing.outputBelow200k : pricing.outputAbove200k;
|
|
645
|
+
}
|
|
646
|
+
return pricing.output ?? pricing.outputBelow200k ?? pricing.outputAbove200k;
|
|
647
|
+
}
|
|
648
|
+
function aiProfilesPricingToCatalogPricing(pricing, pricedAt, options = {}) {
|
|
649
|
+
const promptTokens = options.promptTokens ?? 0;
|
|
650
|
+
const promptUsdPerToken = usdPerToken(pickInputRatePerMillion(pricing, promptTokens));
|
|
651
|
+
const completionUsdPerToken = usdPerToken(
|
|
652
|
+
pickOutputRatePerMillion(pricing, promptTokens)
|
|
653
|
+
);
|
|
654
|
+
const snapshot = {
|
|
655
|
+
promptUsdPerToken,
|
|
656
|
+
completionUsdPerToken,
|
|
657
|
+
cacheReadUsdPerToken: usdPerToken(pricing.cachedInput ?? pricing.cacheHit),
|
|
658
|
+
cacheWriteUsdPerToken: usdPerToken(pricing.cacheWrite5m ?? pricing.cacheWrite1h),
|
|
659
|
+
imageUsdPerUnit: 0,
|
|
660
|
+
requestUsdPerRequest: 0,
|
|
661
|
+
pricedAt,
|
|
662
|
+
source: "manual"
|
|
663
|
+
};
|
|
664
|
+
if (options.routedViaOpenRouter) {
|
|
665
|
+
snapshot.openRouterMarkupUsdPerInputToken = promptUsdPerToken * DEFAULT_OPENROUTER_MARKUP_RATE;
|
|
666
|
+
snapshot.openRouterMarkupUsdPerOutputToken = completionUsdPerToken * DEFAULT_OPENROUTER_MARKUP_RATE;
|
|
667
|
+
}
|
|
668
|
+
return snapshot;
|
|
669
|
+
}
|
|
670
|
+
function computeCostFromPricing(input, pricing, routedViaOpenRouter, includeBreakdown) {
|
|
671
|
+
const { tokens } = input;
|
|
672
|
+
let promptCost = tokens.prompt * pricing.promptUsdPerToken;
|
|
673
|
+
let completionCost = tokens.completion * pricing.completionUsdPerToken;
|
|
674
|
+
const cachingCost = (tokens.cacheWrite ?? 0) * (pricing.cacheWriteUsdPerToken ?? 0) + (tokens.cached ?? 0) * (pricing.cacheReadUsdPerToken ?? 0);
|
|
675
|
+
const reasoningCost = (tokens.reasoning ?? 0) * (pricing.reasoningUsdPerToken ?? pricing.promptUsdPerToken);
|
|
676
|
+
const audioCost = (tokens.audio ?? 0) * pricing.promptUsdPerToken;
|
|
677
|
+
const imageCost = (tokens.image ?? 0) * (pricing.imageUsdPerUnit ?? 0);
|
|
678
|
+
const requestFlat = pricing.requestUsdPerRequest;
|
|
679
|
+
if (routedViaOpenRouter) {
|
|
680
|
+
promptCost += tokens.prompt * (pricing.openRouterMarkupUsdPerInputToken ?? 0);
|
|
681
|
+
completionCost += tokens.completion * (pricing.openRouterMarkupUsdPerOutputToken ?? 0);
|
|
682
|
+
}
|
|
683
|
+
const cost = promptCost + completionCost + cachingCost + reasoningCost + audioCost + imageCost + requestFlat;
|
|
684
|
+
if (!includeBreakdown) {
|
|
685
|
+
return { cost };
|
|
686
|
+
}
|
|
687
|
+
return {
|
|
688
|
+
cost,
|
|
689
|
+
breakdown: {
|
|
690
|
+
promptCostUsd: promptCost,
|
|
691
|
+
completionCostUsd: completionCost,
|
|
692
|
+
cachingCostUsd: cachingCost || void 0,
|
|
693
|
+
reasoningCostUsd: reasoningCost || void 0,
|
|
694
|
+
audioCostUsd: audioCost || void 0,
|
|
695
|
+
imageCostUsd: imageCost || void 0,
|
|
696
|
+
requestFlatCostUsd: requestFlat || void 0
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/cost/CostCalculator.ts
|
|
702
|
+
function emptyPricing() {
|
|
703
|
+
return {
|
|
704
|
+
promptUsdPerToken: 0,
|
|
705
|
+
completionUsdPerToken: 0,
|
|
706
|
+
imageUsdPerUnit: 0,
|
|
707
|
+
requestUsdPerRequest: 0,
|
|
708
|
+
pricedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
709
|
+
source: "manual"
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
function mergeAttempts(...groups) {
|
|
713
|
+
const seen = /* @__PURE__ */ new Set();
|
|
714
|
+
const out = [];
|
|
715
|
+
for (const group of groups) {
|
|
716
|
+
for (const a of group) {
|
|
717
|
+
const key = `${a.provider ?? ""}\0${a.model}`;
|
|
718
|
+
if (seen.has(key)) continue;
|
|
719
|
+
seen.add(key);
|
|
720
|
+
out.push(a);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return out;
|
|
724
|
+
}
|
|
725
|
+
var CostCalculator = class {
|
|
726
|
+
constructor(catalog, options = {}) {
|
|
727
|
+
this.catalog = catalog;
|
|
728
|
+
this.aliasRegistry = options.aliasRegistry;
|
|
729
|
+
this.includeBreakdown = options.includeBreakdown ?? true;
|
|
730
|
+
this.resolverOptions = options.resolverOptions;
|
|
731
|
+
this.throwOnUnknownModel = options.throwOnUnknownModel ?? false;
|
|
732
|
+
}
|
|
733
|
+
catalog;
|
|
734
|
+
aliasRegistry;
|
|
735
|
+
includeBreakdown;
|
|
736
|
+
resolverOptions;
|
|
737
|
+
throwOnUnknownModel;
|
|
738
|
+
/**
|
|
739
|
+
* Extract model, provider, and token usage from an activity / gateway record
|
|
740
|
+
* (MongoDB export, nested `outer`, `config`, `metadata.diagnostics`, etc.)
|
|
741
|
+
* then run {@link calculate}.
|
|
742
|
+
*/
|
|
743
|
+
async calculateFromRecord(record, extractOptions) {
|
|
744
|
+
const { input, provenance } = extractUsageInput(record, extractOptions);
|
|
745
|
+
const warnings = [];
|
|
746
|
+
if (provenance.tokens.estimated) {
|
|
747
|
+
warnings.push({
|
|
748
|
+
code: "ESTIMATED_TOKEN_USAGE",
|
|
749
|
+
message: `Token counts are estimates from ${provenance.tokens.path} (not billed usage).`
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
const result = await this.calculate(input);
|
|
753
|
+
const enriched = enrichCostResult(result, input, toCostExtraction(provenance));
|
|
754
|
+
if (warnings.length > 0) {
|
|
755
|
+
enriched.warnings = [...enriched.warnings ?? [], ...warnings];
|
|
756
|
+
}
|
|
757
|
+
return enriched;
|
|
758
|
+
}
|
|
759
|
+
async calculate(input) {
|
|
760
|
+
const modelInput = resolveUsageModel(input);
|
|
761
|
+
const resolverOpts = {
|
|
762
|
+
...this.resolverOptions,
|
|
763
|
+
aliasRegistry: this.aliasRegistry ?? this.resolverOptions?.aliasRegistry
|
|
764
|
+
};
|
|
765
|
+
let resolved = await resolveFromCatalogAttempts(
|
|
766
|
+
this.catalog,
|
|
767
|
+
buildCatalogResolveAttempts(modelInput, input.provider),
|
|
768
|
+
resolverOpts
|
|
769
|
+
);
|
|
770
|
+
if (isLocalProviderResolution(resolved, input.provider)) {
|
|
771
|
+
return this.localProviderResult(modelInput, input, resolved);
|
|
772
|
+
}
|
|
773
|
+
let profileMatch = null;
|
|
774
|
+
const warnings = [];
|
|
775
|
+
if (!resolved?.found || !resolved.record) {
|
|
776
|
+
if (!isLocalProvider(input.provider)) {
|
|
777
|
+
profileMatch = await matchModelInAiProfiles(modelInput, input.provider);
|
|
778
|
+
}
|
|
779
|
+
if (profileMatch) {
|
|
780
|
+
resolved = await resolveFromCatalogAttempts(
|
|
781
|
+
this.catalog,
|
|
782
|
+
mergeAttempts(
|
|
783
|
+
buildCatalogResolveAttempts(
|
|
784
|
+
profileMatch.canonicalModelId,
|
|
785
|
+
input.provider ?? profileMatch.provider
|
|
786
|
+
),
|
|
787
|
+
buildCatalogResolveAttempts(
|
|
788
|
+
profileMatch.canonicalModelId,
|
|
789
|
+
profileMatch.provider
|
|
790
|
+
)
|
|
791
|
+
),
|
|
792
|
+
resolverOpts
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
if (isLocalProviderResolution(resolved, input.provider)) {
|
|
796
|
+
return this.localProviderResult(modelInput, input, resolved);
|
|
797
|
+
}
|
|
798
|
+
if ((!resolved?.found || !resolved.record) && profileMatch?.pricing) {
|
|
799
|
+
const routedViaOpenRouter2 = resolveRoutedViaOpenRouter(
|
|
800
|
+
input.provider,
|
|
801
|
+
resolved,
|
|
802
|
+
profileMatch.canonicalModelId,
|
|
803
|
+
resolverOpts?.routingEnv
|
|
804
|
+
);
|
|
805
|
+
const pricing2 = aiProfilesPricingToCatalogPricing(
|
|
806
|
+
profileMatch.pricing,
|
|
807
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
808
|
+
{
|
|
809
|
+
promptTokens: input.tokens.prompt,
|
|
810
|
+
routedViaOpenRouter: routedViaOpenRouter2
|
|
811
|
+
}
|
|
812
|
+
);
|
|
813
|
+
const { cost: cost2, breakdown: breakdown2 } = computeCostFromPricing(
|
|
814
|
+
input,
|
|
815
|
+
pricing2,
|
|
816
|
+
routedViaOpenRouter2,
|
|
817
|
+
this.includeBreakdown
|
|
818
|
+
);
|
|
819
|
+
warnings.push({
|
|
820
|
+
code: "AI_PROFILES_ESTIMATE",
|
|
821
|
+
message: `Catalog has no pricing for "${modelInput}"; estimated from ai-profiles (${profileMatch.matchedVia} \u2192 ${profileMatch.canonicalModelId}).`
|
|
822
|
+
});
|
|
823
|
+
if (routedViaOpenRouter2 && pricing2.openRouterMarkupUsdPerInputToken !== void 0) {
|
|
824
|
+
warnings.push({
|
|
825
|
+
code: "OPENROUTER_MARKUP_ESTIMATED",
|
|
826
|
+
message: "OpenRouter markup estimated at 5% of base token rates (catalog markup fields absent)."
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
console.warn(`[ai-tools] ${warnings.map((w) => w.message).join(" ")}`);
|
|
830
|
+
return this.finish(
|
|
831
|
+
{
|
|
832
|
+
cost: cost2,
|
|
833
|
+
breakdown: breakdown2,
|
|
834
|
+
resolvedModelId: profileMatch.canonicalModelId,
|
|
835
|
+
routedViaOpenRouter: routedViaOpenRouter2,
|
|
836
|
+
isAuthoritative: false,
|
|
837
|
+
pricingSnapshot: pricing2,
|
|
838
|
+
source: "ai-profiles",
|
|
839
|
+
warnings
|
|
840
|
+
},
|
|
841
|
+
input,
|
|
842
|
+
resolved
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (!resolved?.found || !resolved.record) {
|
|
847
|
+
return this.unknownModelResult(modelInput, input, resolved);
|
|
848
|
+
}
|
|
849
|
+
const { record, modelId } = resolved;
|
|
850
|
+
const routedViaOpenRouter = resolveRoutedViaOpenRouter(
|
|
851
|
+
input.provider,
|
|
852
|
+
resolved,
|
|
853
|
+
modelId,
|
|
854
|
+
resolverOpts?.routingEnv
|
|
855
|
+
);
|
|
856
|
+
const pricing = record.pricing;
|
|
857
|
+
const { cost, breakdown } = computeCostFromPricing(
|
|
858
|
+
input,
|
|
859
|
+
pricing,
|
|
860
|
+
routedViaOpenRouter,
|
|
861
|
+
this.includeBreakdown
|
|
862
|
+
);
|
|
863
|
+
return this.finish(
|
|
864
|
+
{
|
|
865
|
+
cost,
|
|
866
|
+
breakdown,
|
|
867
|
+
resolvedModelId: modelId,
|
|
868
|
+
routedViaOpenRouter,
|
|
869
|
+
isAuthoritative: true,
|
|
870
|
+
pricingSnapshot: pricing,
|
|
871
|
+
source: "catalog"
|
|
872
|
+
},
|
|
873
|
+
input,
|
|
874
|
+
resolved
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
versionSuffixWarnings(input, pricedModelId, resolved) {
|
|
878
|
+
const received = input.usedModel ?? input.modelUsed ?? input.model;
|
|
879
|
+
if (!received) return [];
|
|
880
|
+
const normalizedReceived = normalizeString(received);
|
|
881
|
+
const normalizedPriced = normalizeString(pricedModelId);
|
|
882
|
+
if (normalizedReceived === normalizedPriced) return [];
|
|
883
|
+
const stripped = stripModelVersionSuffix(received);
|
|
884
|
+
const viaResolver = resolved?.found === true && resolved.resolvedVia.some(
|
|
885
|
+
(s) => s === "version-suffix-strip" || s === "date-suffix-strip"
|
|
886
|
+
);
|
|
887
|
+
const pricedViaStrip = stripped !== null && normalizeString(stripped) === normalizedPriced;
|
|
888
|
+
if (!viaResolver && !pricedViaStrip) return [];
|
|
889
|
+
return [
|
|
890
|
+
{
|
|
891
|
+
code: "VERSION_SUFFIX_PRICING",
|
|
892
|
+
message: `Priced using catalog model "${pricedModelId}" (no exact pricing for "${received}").`
|
|
893
|
+
}
|
|
894
|
+
];
|
|
895
|
+
}
|
|
896
|
+
finish(partial, input, resolved) {
|
|
897
|
+
const enriched = enrichCostResult(partial, input);
|
|
898
|
+
const suffixWarnings = this.versionSuffixWarnings(
|
|
899
|
+
input,
|
|
900
|
+
partial.resolvedModelId,
|
|
901
|
+
resolved
|
|
902
|
+
);
|
|
903
|
+
if (suffixWarnings.length > 0) {
|
|
904
|
+
enriched.warnings = [...enriched.warnings ?? [], ...suffixWarnings];
|
|
905
|
+
}
|
|
906
|
+
return enriched;
|
|
907
|
+
}
|
|
908
|
+
localProviderResult(modelInput, input, resolved) {
|
|
909
|
+
const warnings = [
|
|
910
|
+
{
|
|
911
|
+
code: "LOCAL_PROVIDER_NO_PRICING",
|
|
912
|
+
message: `Local provider "${input.provider}" has no catalog pricing for "${modelInput}".`
|
|
913
|
+
}
|
|
914
|
+
];
|
|
915
|
+
console.warn(`[ai-tools] ${warnings[0].message}`);
|
|
916
|
+
return this.finish(
|
|
917
|
+
{
|
|
918
|
+
cost: 0,
|
|
919
|
+
resolvedModelId: resolved?.found ? resolved.modelId : modelInput,
|
|
920
|
+
routedViaOpenRouter: false,
|
|
921
|
+
isAuthoritative: false,
|
|
922
|
+
pricingSnapshot: emptyPricing(),
|
|
923
|
+
source: "local",
|
|
924
|
+
warnings
|
|
925
|
+
},
|
|
926
|
+
input
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
unknownModelResult(modelInput, input, resolved) {
|
|
930
|
+
if (this.throwOnUnknownModel) {
|
|
931
|
+
throw new UnknownModelCostError(modelInput, input.provider);
|
|
932
|
+
}
|
|
933
|
+
const warnings = [
|
|
934
|
+
{
|
|
935
|
+
code: "UNKNOWN_MODEL",
|
|
936
|
+
message: `Unknown model "${modelInput}" (provider: "${input.provider}") \u2014 returning zero-cost fallback.`
|
|
937
|
+
}
|
|
938
|
+
];
|
|
939
|
+
console.warn(`[ai-tools] ${warnings[0].message}`);
|
|
940
|
+
return this.finish(
|
|
941
|
+
{
|
|
942
|
+
cost: 0,
|
|
943
|
+
resolvedModelId: modelInput,
|
|
944
|
+
routedViaOpenRouter: resolveRoutedViaOpenRouter(
|
|
945
|
+
input.provider,
|
|
946
|
+
resolved,
|
|
947
|
+
modelInput,
|
|
948
|
+
this.resolverOptions?.routingEnv
|
|
949
|
+
),
|
|
950
|
+
isAuthoritative: false,
|
|
951
|
+
pricingSnapshot: emptyPricing(),
|
|
952
|
+
source: "estimate-fallback",
|
|
953
|
+
unknownModel: true,
|
|
954
|
+
warnings
|
|
955
|
+
},
|
|
956
|
+
input
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
|
|
961
|
+
export {
|
|
962
|
+
DEFAULT_OPENROUTER_MARKUP_RATE,
|
|
963
|
+
toCostExtraction,
|
|
964
|
+
enrichCostResult,
|
|
965
|
+
MODEL_FIELD_PRIORITY,
|
|
966
|
+
extractUsageInput,
|
|
967
|
+
resolveUsageModel,
|
|
968
|
+
CostCalculator
|
|
969
|
+
};
|
|
970
|
+
//# sourceMappingURL=chunk-U2YDDUVP.js.map
|