@cendor/core 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +53 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/bus.d.ts +21 -0
- package/dist/bus.d.ts.map +1 -0
- package/dist/bus.js +48 -0
- package/dist/bus.js.map +1 -0
- package/dist/decimal.d.ts +11 -0
- package/dist/decimal.d.ts.map +1 -0
- package/dist/decimal.js +13 -0
- package/dist/decimal.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/instrument.d.ts +26 -0
- package/dist/instrument.d.ts.map +1 -0
- package/dist/instrument.js +478 -0
- package/dist/instrument.js.map +1 -0
- package/dist/json-decimal.d.ts +12 -0
- package/dist/json-decimal.d.ts.map +1 -0
- package/dist/json-decimal.js +189 -0
- package/dist/json-decimal.js.map +1 -0
- package/dist/prices-snapshot.d.ts +8 -0
- package/dist/prices-snapshot.d.ts.map +1 -0
- package/dist/prices-snapshot.js +33 -0
- package/dist/prices-snapshot.js.map +1 -0
- package/dist/prices.d.ts +78 -0
- package/dist/prices.d.ts.map +1 -0
- package/dist/prices.js +311 -0
- package/dist/prices.js.map +1 -0
- package/dist/protocols.d.ts +33 -0
- package/dist/protocols.d.ts.map +1 -0
- package/dist/protocols.js +6 -0
- package/dist/protocols.js.map +1 -0
- package/dist/tokens.d.ts +20 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +161 -0
- package/dist/tokens.js.map +1 -0
- package/dist/trace.d.ts +35 -0
- package/dist/trace.d.ts.map +1 -0
- package/dist/trace.js +59 -0
- package/dist/trace.js.map +1 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +146 -0
- package/dist/types.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A JSON parser that decodes every number as a `Decimal` instead of an IEEE double — the TS mirror of
|
|
3
|
+
* Python's `json.loads(text, parse_float=Decimal)`. The price-dataset spec mandates that rates (money)
|
|
4
|
+
* never round-trip through binary floating point in any language, so both the bundled snapshot and any
|
|
5
|
+
* `refresh()`ed table are parsed through this. Dependency-free recursive-descent parser.
|
|
6
|
+
*/
|
|
7
|
+
import { Dec } from './decimal.js';
|
|
8
|
+
class Parser {
|
|
9
|
+
s;
|
|
10
|
+
i = 0;
|
|
11
|
+
constructor(s) {
|
|
12
|
+
this.s = s;
|
|
13
|
+
}
|
|
14
|
+
parse() {
|
|
15
|
+
this.ws();
|
|
16
|
+
const value = this.value();
|
|
17
|
+
this.ws();
|
|
18
|
+
if (this.i !== this.s.length)
|
|
19
|
+
throw new SyntaxError(`Unexpected trailing content at ${this.i}`);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
ws() {
|
|
23
|
+
while (this.i < this.s.length) {
|
|
24
|
+
const c = this.s.charCodeAt(this.i);
|
|
25
|
+
// space, tab, newline, carriage return
|
|
26
|
+
if (c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0d)
|
|
27
|
+
this.i++;
|
|
28
|
+
else
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
value() {
|
|
33
|
+
const c = this.s[this.i];
|
|
34
|
+
if (c === '{')
|
|
35
|
+
return this.object();
|
|
36
|
+
if (c === '[')
|
|
37
|
+
return this.array();
|
|
38
|
+
if (c === '"')
|
|
39
|
+
return this.string();
|
|
40
|
+
if (c === '-' || (c !== undefined && c >= '0' && c <= '9'))
|
|
41
|
+
return this.number();
|
|
42
|
+
if (this.s.startsWith('true', this.i)) {
|
|
43
|
+
this.i += 4;
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (this.s.startsWith('false', this.i)) {
|
|
47
|
+
this.i += 5;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (this.s.startsWith('null', this.i)) {
|
|
51
|
+
this.i += 4;
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
throw new SyntaxError(`Unexpected token '${c}' at ${this.i}`);
|
|
55
|
+
}
|
|
56
|
+
object() {
|
|
57
|
+
const obj = {};
|
|
58
|
+
this.i++; // {
|
|
59
|
+
this.ws();
|
|
60
|
+
if (this.s[this.i] === '}') {
|
|
61
|
+
this.i++;
|
|
62
|
+
return obj;
|
|
63
|
+
}
|
|
64
|
+
for (;;) {
|
|
65
|
+
this.ws();
|
|
66
|
+
if (this.s[this.i] !== '"')
|
|
67
|
+
throw new SyntaxError(`Expected key string at ${this.i}`);
|
|
68
|
+
const key = this.string();
|
|
69
|
+
this.ws();
|
|
70
|
+
if (this.s[this.i] !== ':')
|
|
71
|
+
throw new SyntaxError(`Expected ':' at ${this.i}`);
|
|
72
|
+
this.i++;
|
|
73
|
+
this.ws();
|
|
74
|
+
obj[key] = this.value();
|
|
75
|
+
this.ws();
|
|
76
|
+
const ch = this.s[this.i];
|
|
77
|
+
if (ch === ',') {
|
|
78
|
+
this.i++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (ch === '}') {
|
|
82
|
+
this.i++;
|
|
83
|
+
return obj;
|
|
84
|
+
}
|
|
85
|
+
throw new SyntaxError(`Expected ',' or '}' at ${this.i}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
array() {
|
|
89
|
+
const arr = [];
|
|
90
|
+
this.i++; // [
|
|
91
|
+
this.ws();
|
|
92
|
+
if (this.s[this.i] === ']') {
|
|
93
|
+
this.i++;
|
|
94
|
+
return arr;
|
|
95
|
+
}
|
|
96
|
+
for (;;) {
|
|
97
|
+
this.ws();
|
|
98
|
+
arr.push(this.value());
|
|
99
|
+
this.ws();
|
|
100
|
+
const ch = this.s[this.i];
|
|
101
|
+
if (ch === ',') {
|
|
102
|
+
this.i++;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (ch === ']') {
|
|
106
|
+
this.i++;
|
|
107
|
+
return arr;
|
|
108
|
+
}
|
|
109
|
+
throw new SyntaxError(`Expected ',' or ']' at ${this.i}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
string() {
|
|
113
|
+
this.i++; // opening quote
|
|
114
|
+
let out = '';
|
|
115
|
+
for (;;) {
|
|
116
|
+
const c = this.s[this.i++];
|
|
117
|
+
if (c === undefined)
|
|
118
|
+
throw new SyntaxError('Unterminated string');
|
|
119
|
+
if (c === '"')
|
|
120
|
+
return out;
|
|
121
|
+
if (c === '\\') {
|
|
122
|
+
const e = this.s[this.i++];
|
|
123
|
+
switch (e) {
|
|
124
|
+
case '"':
|
|
125
|
+
out += '"';
|
|
126
|
+
break;
|
|
127
|
+
case '\\':
|
|
128
|
+
out += '\\';
|
|
129
|
+
break;
|
|
130
|
+
case '/':
|
|
131
|
+
out += '/';
|
|
132
|
+
break;
|
|
133
|
+
case 'b':
|
|
134
|
+
out += '\b';
|
|
135
|
+
break;
|
|
136
|
+
case 'f':
|
|
137
|
+
out += '\f';
|
|
138
|
+
break;
|
|
139
|
+
case 'n':
|
|
140
|
+
out += '\n';
|
|
141
|
+
break;
|
|
142
|
+
case 'r':
|
|
143
|
+
out += '\r';
|
|
144
|
+
break;
|
|
145
|
+
case 't':
|
|
146
|
+
out += '\t';
|
|
147
|
+
break;
|
|
148
|
+
case 'u': {
|
|
149
|
+
const hex = this.s.slice(this.i, this.i + 4);
|
|
150
|
+
this.i += 4;
|
|
151
|
+
out += String.fromCharCode(Number.parseInt(hex, 16));
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
default:
|
|
155
|
+
throw new SyntaxError(`Bad escape \\${e}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
out += c;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
number() {
|
|
164
|
+
const start = this.i;
|
|
165
|
+
if (this.s[this.i] === '-')
|
|
166
|
+
this.i++;
|
|
167
|
+
while (this.i < this.s.length) {
|
|
168
|
+
const c = this.s[this.i];
|
|
169
|
+
if ((c !== undefined && c >= '0' && c <= '9') ||
|
|
170
|
+
c === '.' ||
|
|
171
|
+
c === 'e' ||
|
|
172
|
+
c === 'E' ||
|
|
173
|
+
c === '+' ||
|
|
174
|
+
c === '-') {
|
|
175
|
+
this.i++;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const token = this.s.slice(start, this.i);
|
|
182
|
+
// Pass the raw token straight to Decimal so precision is preserved (never through a JS number).
|
|
183
|
+
return new Dec(token);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
export function parseDecimalJson(text) {
|
|
187
|
+
return new Parser(text).parse();
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=json-decimal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-decimal.js","sourceRoot":"","sources":["../src/json-decimal.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,GAAG,EAAgB,MAAM,cAAc,CAAC;AAUjD,MAAM,MAAM;IAEmB;IADrB,CAAC,GAAG,CAAC,CAAC;IACd,YAA6B,CAAS;QAAT,MAAC,GAAD,CAAC,CAAQ;IAAG,CAAC;IAE1C,KAAK;QACH,IAAI,CAAC,EAAE,EAAE,CAAC;QACV,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;QAC3B,IAAI,CAAC,EAAE,EAAE,CAAC;QACV,IAAI,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM;YAAE,MAAM,IAAI,WAAW,CAAC,kCAAkC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QAChG,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,EAAE;QACR,OAAO,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;YAC9B,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACpC,uCAAuC;YACvC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI;gBAAE,IAAI,CAAC,CAAC,EAAE,CAAC;;gBAC9D,MAAM;QACb,CAAC;IACH,CAAC;IAEO,KAAK;QACX,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;QACpC,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;QACnC,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;QACpC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,KAAK,SAAS,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;QACjF,IAAI,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;YACZ,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACvC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;YACZ,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;YACZ,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,IAAI,WAAW,CAAC,qBAAqB,CAAC,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IAChE,CAAC;IAEO,MAAM;QACZ,MAAM,GAAG,GAAwC,EAAE,CAAC;QACpD,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI;QACd,IAAI,CAAC,EAAE,EAAE,CAAC;QACV,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YAC3B,IAAI,CAAC,CAAC,EAAE,CAAC;YACT,OAAO,GAAG,CAAC;QACb,CAAC;QACD,SAAS,CAAC;YACR,IAAI,CAAC,EAAE,EAAE,CAAC;YACV,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;gBAAE,MAAM,IAAI,WAAW,CAAC,0BAA0B,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;YACtF,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1B,IAAI,CAAC,EAAE,EAAE,CAAC;YACV,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;gBAAE,MAAM,IAAI,WAAW,CAAC,mBAAmB,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;YAC/E,IAAI,CAAC,CAAC,EAAE,CAAC;YACT,IAAI,CAAC,EAAE,EAAE,CAAC;YACV,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YACxB,IAAI,CAAC,EAAE,EAAE,CAAC;YACV,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,IAAI,CAAC,CAAC,EAAE,CAAC;gBACT,SAAS;YACX,CAAC;YACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,IAAI,CAAC,CAAC,EAAE,CAAC;gBACT,OAAO,GAAG,CAAC;YACb,CAAC;YACD,MAAM,IAAI,WAAW,CAAC,0BAA0B,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAEO,KAAK;QACX,MAAM,GAAG,GAAuB,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI;QACd,IAAI,CAAC,EAAE,EAAE,CAAC;QACV,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YAC3B,IAAI,CAAC,CAAC,EAAE,CAAC;YACT,OAAO,GAAG,CAAC;QACb,CAAC;QACD,SAAS,CAAC;YACR,IAAI,CAAC,EAAE,EAAE,CAAC;YACV,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;YACvB,IAAI,CAAC,EAAE,EAAE,CAAC;YACV,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,IAAI,CAAC,CAAC,EAAE,CAAC;gBACT,SAAS;YACX,CAAC;YACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,IAAI,CAAC,CAAC,EAAE,CAAC;gBACT,OAAO,GAAG,CAAC;YACb,CAAC;YACD,MAAM,IAAI,WAAW,CAAC,0BAA0B,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAEO,MAAM;QACZ,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,gBAAgB;QAC1B,IAAI,GAAG,GAAG,EAAE,CAAC;QACb,SAAS,CAAC;YACR,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;YAC3B,IAAI,CAAC,KAAK,SAAS;gBAAE,MAAM,IAAI,WAAW,CAAC,qBAAqB,CAAC,CAAC;YAClE,IAAI,CAAC,KAAK,GAAG;gBAAE,OAAO,GAAG,CAAC;YAC1B,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;gBACf,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;gBAC3B,QAAQ,CAAC,EAAE,CAAC;oBACV,KAAK,GAAG;wBACN,GAAG,IAAI,GAAG,CAAC;wBACX,MAAM;oBACR,KAAK,IAAI;wBACP,GAAG,IAAI,IAAI,CAAC;wBACZ,MAAM;oBACR,KAAK,GAAG;wBACN,GAAG,IAAI,GAAG,CAAC;wBACX,MAAM;oBACR,KAAK,GAAG;wBACN,GAAG,IAAI,IAAI,CAAC;wBACZ,MAAM;oBACR,KAAK,GAAG;wBACN,GAAG,IAAI,IAAI,CAAC;wBACZ,MAAM;oBACR,KAAK,GAAG;wBACN,GAAG,IAAI,IAAI,CAAC;wBACZ,MAAM;oBACR,KAAK,GAAG;wBACN,GAAG,IAAI,IAAI,CAAC;wBACZ,MAAM;oBACR,KAAK,GAAG;wBACN,GAAG,IAAI,IAAI,CAAC;wBACZ,MAAM;oBACR,KAAK,GAAG,CAAC,CAAC,CAAC;wBACT,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;wBAC7C,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;wBACZ,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;wBACrD,MAAM;oBACR,CAAC;oBACD;wBACE,MAAM,IAAI,WAAW,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;gBAC/C,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,GAAG,IAAI,CAAC,CAAC;YACX,CAAC;QACH,CAAC;IACH,CAAC;IAEO,MAAM;QACZ,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC;QACrB,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,IAAI,CAAC,CAAC,EAAE,CAAC;QACrC,OAAO,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;YAC9B,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACzB,IACE,CAAC,CAAC,KAAK,SAAS,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC;gBACzC,CAAC,KAAK,GAAG;gBACT,CAAC,KAAK,GAAG;gBACT,CAAC,KAAK,GAAG;gBACT,CAAC,KAAK,GAAG;gBACT,CAAC,KAAK,GAAG,EACT,CAAC;gBACD,IAAI,CAAC,CAAC,EAAE,CAAC;YACX,CAAC;iBAAM,CAAC;gBACN,MAAM;YACR,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1C,gGAAgG;QAChG,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;CACF;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bundled offline price snapshot, embedded verbatim from the Python `cendor-core` `prices.json`
|
|
3
|
+
* so cost estimation works with zero network and zero filesystem access (edge/browser-safe). Parsed
|
|
4
|
+
* through {@link parseDecimalJson} so rates stay exact `Decimal`s. Refresh a live table with
|
|
5
|
+
* `prices.refresh(...)`. Format is pinned by the price-dataset spec (`prices/1`).
|
|
6
|
+
*/
|
|
7
|
+
export declare const PRICES_JSON = "{\n \"_note\": \"Illustrative offline snapshot of per-token USD rates. Not authoritative \u2014 refresh live via prices.refresh(source='litellm'|'openrouter'|'azure') or prices.refresh(url=...), or replace with your own dated snapshot. See docs/core.md \u00A77.\",\n \"_updated\": \"2026-06-26\",\n \"models\": {\n \"gpt-4o\": {\"input\": 0.0000025, \"output\": 0.00001, \"cached\": 0.00000125},\n \"gpt-4o-mini\": {\"input\": 0.00000015, \"output\": 0.0000006, \"cached\": 0.000000075},\n \"gpt-4.1\": {\"input\": 0.000002, \"output\": 0.000008, \"cached\": 0.0000005},\n \"gpt-4.1-mini\": {\"input\": 0.0000004, \"output\": 0.0000016, \"cached\": 0.0000001},\n \"gpt-4.1-nano\": {\"input\": 0.0000001, \"output\": 0.0000004, \"cached\": 0.000000025},\n \"o1\": {\"input\": 0.000015, \"output\": 0.00006, \"cached\": 0.0000075},\n \"o1-mini\": {\"input\": 0.0000011, \"output\": 0.0000044, \"cached\": 0.00000055},\n \"o3\": {\"input\": 0.000002, \"output\": 0.000008, \"cached\": 0.0000005},\n \"o3-mini\": {\"input\": 0.0000011, \"output\": 0.0000044, \"cached\": 0.00000055},\n \"o4-mini\": {\"input\": 0.0000011, \"output\": 0.0000044, \"cached\": 0.000000275},\n \"gpt-4-turbo\": {\"input\": 0.00001, \"output\": 0.00003},\n \"gpt-3.5-turbo\": {\"input\": 0.0000005, \"output\": 0.0000015},\n \"claude-opus-4-8\": {\"input\": 0.000005, \"output\": 0.000025, \"cached\": 0.0000005, \"cache_write\": 0.00000625},\n \"claude-sonnet-4-6\": {\"input\": 0.000003, \"output\": 0.000015, \"cached\": 0.0000003, \"cache_write\": 0.00000375},\n \"claude-haiku-4-5\": {\"input\": 0.0000008, \"output\": 0.000004, \"cached\": 0.00000008, \"cache_write\": 0.000001},\n \"gemini-2.5-pro\": {\"input\": 0.00000125, \"output\": 0.00001, \"cached\": 0.0000003125},\n \"gemini-2.5-flash\": {\"input\": 0.0000003, \"output\": 0.0000025, \"cached\": 0.000000075},\n \"gemini-2.0-flash\": {\"input\": 0.0000001, \"output\": 0.0000004},\n \"gemini-1.5-pro\": {\"input\": 0.00000125, \"output\": 0.000005},\n \"llama3\": {\"input\": 0.0, \"output\": 0.0}\n }\n}";
|
|
8
|
+
//# sourceMappingURL=prices-snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prices-snapshot.d.ts","sourceRoot":"","sources":["../src/prices-snapshot.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,WAAW,2uEAyBtB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bundled offline price snapshot, embedded verbatim from the Python `cendor-core` `prices.json`
|
|
3
|
+
* so cost estimation works with zero network and zero filesystem access (edge/browser-safe). Parsed
|
|
4
|
+
* through {@link parseDecimalJson} so rates stay exact `Decimal`s. Refresh a live table with
|
|
5
|
+
* `prices.refresh(...)`. Format is pinned by the price-dataset spec (`prices/1`).
|
|
6
|
+
*/
|
|
7
|
+
export const PRICES_JSON = `{
|
|
8
|
+
"_note": "Illustrative offline snapshot of per-token USD rates. Not authoritative — refresh live via prices.refresh(source='litellm'|'openrouter'|'azure') or prices.refresh(url=...), or replace with your own dated snapshot. See docs/core.md §7.",
|
|
9
|
+
"_updated": "2026-06-26",
|
|
10
|
+
"models": {
|
|
11
|
+
"gpt-4o": {"input": 0.0000025, "output": 0.00001, "cached": 0.00000125},
|
|
12
|
+
"gpt-4o-mini": {"input": 0.00000015, "output": 0.0000006, "cached": 0.000000075},
|
|
13
|
+
"gpt-4.1": {"input": 0.000002, "output": 0.000008, "cached": 0.0000005},
|
|
14
|
+
"gpt-4.1-mini": {"input": 0.0000004, "output": 0.0000016, "cached": 0.0000001},
|
|
15
|
+
"gpt-4.1-nano": {"input": 0.0000001, "output": 0.0000004, "cached": 0.000000025},
|
|
16
|
+
"o1": {"input": 0.000015, "output": 0.00006, "cached": 0.0000075},
|
|
17
|
+
"o1-mini": {"input": 0.0000011, "output": 0.0000044, "cached": 0.00000055},
|
|
18
|
+
"o3": {"input": 0.000002, "output": 0.000008, "cached": 0.0000005},
|
|
19
|
+
"o3-mini": {"input": 0.0000011, "output": 0.0000044, "cached": 0.00000055},
|
|
20
|
+
"o4-mini": {"input": 0.0000011, "output": 0.0000044, "cached": 0.000000275},
|
|
21
|
+
"gpt-4-turbo": {"input": 0.00001, "output": 0.00003},
|
|
22
|
+
"gpt-3.5-turbo": {"input": 0.0000005, "output": 0.0000015},
|
|
23
|
+
"claude-opus-4-8": {"input": 0.000005, "output": 0.000025, "cached": 0.0000005, "cache_write": 0.00000625},
|
|
24
|
+
"claude-sonnet-4-6": {"input": 0.000003, "output": 0.000015, "cached": 0.0000003, "cache_write": 0.00000375},
|
|
25
|
+
"claude-haiku-4-5": {"input": 0.0000008, "output": 0.000004, "cached": 0.00000008, "cache_write": 0.000001},
|
|
26
|
+
"gemini-2.5-pro": {"input": 0.00000125, "output": 0.00001, "cached": 0.0000003125},
|
|
27
|
+
"gemini-2.5-flash": {"input": 0.0000003, "output": 0.0000025, "cached": 0.000000075},
|
|
28
|
+
"gemini-2.0-flash": {"input": 0.0000001, "output": 0.0000004},
|
|
29
|
+
"gemini-1.5-pro": {"input": 0.00000125, "output": 0.000005},
|
|
30
|
+
"llama3": {"input": 0.0, "output": 0.0}
|
|
31
|
+
}
|
|
32
|
+
}`;
|
|
33
|
+
//# sourceMappingURL=prices-snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prices-snapshot.js","sourceRoot":"","sources":["../src/prices-snapshot.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;EAyBzB,CAAC"}
|
package/dist/prices.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline-first price registry: a bundled snapshot plus an optional live `refresh()`. The TS mirror of
|
|
3
|
+
* `cendor.core.prices`. Costs are exact `Decimal` `Money` — never IEEE floats (price-dataset spec
|
|
4
|
+
* `prices/1`). `refresh()` is async here (JS is async-first; Python's sync `urllib` becomes `fetch`).
|
|
5
|
+
*/
|
|
6
|
+
import { type Decimal, type DecimalValue } from './decimal.js';
|
|
7
|
+
import { type DecimalJsonValue } from './json-decimal.js';
|
|
8
|
+
import { Money } from './types.js';
|
|
9
|
+
/** Raised when a model id is not present in the price table. */
|
|
10
|
+
export declare class UnknownModelError extends Error {
|
|
11
|
+
constructor(model: string);
|
|
12
|
+
}
|
|
13
|
+
type Rates = Record<string, Decimal>;
|
|
14
|
+
interface Table {
|
|
15
|
+
_updated?: string;
|
|
16
|
+
models: Record<string, Rates>;
|
|
17
|
+
}
|
|
18
|
+
/** Default static snapshot location used by `refresh()` when no url or source is given. */
|
|
19
|
+
export declare const SNAPSHOT_URL = "https://raw.githubusercontent.com/cendorhq/cendor-libs/main/packages/cendor-core/src/cendor/core/prices.json";
|
|
20
|
+
export declare const LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
21
|
+
export declare const OPENROUTER_URL = "https://openrouter.ai/api/v1/models";
|
|
22
|
+
export declare const AZURE_URL = "https://prices.azure.com/api/retail/prices?api-version=2023-01-01-preview&$filter=productName eq 'Azure OpenAI'";
|
|
23
|
+
export interface EstimateOptions {
|
|
24
|
+
outputTokens?: number;
|
|
25
|
+
cachedTokens?: number;
|
|
26
|
+
cacheWriteTokens?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Estimate the cost of a call from the price snapshot, as exact `Decimal` `Money`. `cachedTokens` is a
|
|
30
|
+
* subset of `inputTokens`, billed once: `input*(input−cached) + cached*cachedRate`. Unknown model
|
|
31
|
+
* throws {@link UnknownModelError}. Mirrors `cendor.core.prices.estimate`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function estimate(model: string, inputTokens: number, opts?: EstimateOptions): Money;
|
|
34
|
+
export interface RegisterRates {
|
|
35
|
+
input: DecimalValue;
|
|
36
|
+
output?: DecimalValue;
|
|
37
|
+
cached?: DecimalValue;
|
|
38
|
+
cache_write?: DecimalValue;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Register (or overwrite) a model's **per-token** rates in the active price table, so a model absent
|
|
42
|
+
* from the bundled snapshot (a custom/deployment/Hub id) is costed and USD budgets bind on it.
|
|
43
|
+
* Rates are exact `Decimal`. The higher-level `@cendor/sdk` `registerModelPrice` handles per-1M/1K
|
|
44
|
+
* unit conversion before calling this. Dropped by {@link _reset}.
|
|
45
|
+
*/
|
|
46
|
+
export declare function register(model: string, rates: RegisterRates): void;
|
|
47
|
+
/** Sorted list of model ids known to the current price table. */
|
|
48
|
+
export declare function models(): string[];
|
|
49
|
+
/** The `_updated` date of the loaded snapshot, or `null`. */
|
|
50
|
+
export declare function snapshotDate(): string | null;
|
|
51
|
+
/** `"bundled"` or `"refreshed"` — where the active table came from. */
|
|
52
|
+
export declare function source(): string;
|
|
53
|
+
/** Finer provenance: `"bundled"` | `"litellm"` | `"openrouter"` | `"azure"` | `"custom"` | `"default"`. */
|
|
54
|
+
export declare function sourceName(): string;
|
|
55
|
+
/** The URL the active table was fetched from, or `null` if it's the bundled snapshot. */
|
|
56
|
+
export declare function sourceUrl(): string | null;
|
|
57
|
+
/** Age of the active table in days (today − `_updated`), or `null` if undatable. */
|
|
58
|
+
export declare function ageDays(today?: Date): number | null;
|
|
59
|
+
/** `true` if the table is older than `maxAgeDays` (an undatable table is never stale). */
|
|
60
|
+
export declare function isStale(maxAgeDays?: number): boolean;
|
|
61
|
+
type Mapper = (raw: DecimalJsonValue) => Table;
|
|
62
|
+
/** Names of the built-in live price sources accepted by `refresh({ source })`. */
|
|
63
|
+
export declare function sources(): string[];
|
|
64
|
+
export interface RefreshOptions {
|
|
65
|
+
source?: string;
|
|
66
|
+
mapper?: Mapper;
|
|
67
|
+
timeout?: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Replace the table from a live source or static JSON URL. Never throws; offline-safe. Returns `true`
|
|
71
|
+
* if the table was updated, `false` if the fetch/parse/map failed (the current table stays active).
|
|
72
|
+
* Mirrors `cendor.core.prices.refresh` (async here: uses `fetch`).
|
|
73
|
+
*/
|
|
74
|
+
export declare function refresh(url?: string, opts?: RefreshOptions): Promise<boolean>;
|
|
75
|
+
/** Test helper: drop the loaded table so the bundled snapshot reloads. */
|
|
76
|
+
export declare function _reset(): void;
|
|
77
|
+
export {};
|
|
78
|
+
//# sourceMappingURL=prices.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prices.d.ts","sourceRoot":"","sources":["../src/prices.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAO,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,KAAK,gBAAgB,EAAoB,MAAM,mBAAmB,CAAC;AAE5E,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,gEAAgE;AAChE,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,KAAK,EAAE,MAAM;CAI1B;AAED,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACrC,UAAU,KAAK;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;CAC/B;AAOD,2FAA2F;AAC3F,eAAO,MAAM,YAAY,iHACuF,CAAC;AACjH,eAAO,MAAM,WAAW,gGACuE,CAAC;AAChG,eAAO,MAAM,cAAc,wCAAwC,CAAC;AACpE,eAAO,MAAM,SAAS,oHAC6F,CAAC;AAmBpH,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,KAAK,CAiB9F;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,WAAW,CAAC,EAAE,YAAY,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAalE;AAED,iEAAiE;AACjE,wBAAgB,MAAM,IAAI,MAAM,EAAE,CAEjC;AAED,6DAA6D;AAC7D,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAE5C;AAED,uEAAuE;AACvE,wBAAgB,MAAM,IAAI,MAAM,CAE/B;AAED,2GAA2G;AAC3G,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,yFAAyF;AACzF,wBAAgB,SAAS,IAAI,MAAM,GAAG,IAAI,CAEzC;AAED,oFAAoF;AACpF,wBAAgB,OAAO,CAAC,KAAK,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAUnD;AAED,0FAA0F;AAC1F,wBAAgB,OAAO,CAAC,UAAU,SAAK,GAAG,OAAO,CAGhD;AA6GD,KAAK,MAAM,GAAG,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC;AAO/C,kFAAkF;AAClF,wBAAgB,OAAO,IAAI,MAAM,EAAE,CAElC;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAwCvF;AAED,0EAA0E;AAC1E,wBAAgB,MAAM,IAAI,IAAI,CAK7B"}
|
package/dist/prices.js
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline-first price registry: a bundled snapshot plus an optional live `refresh()`. The TS mirror of
|
|
3
|
+
* `cendor.core.prices`. Costs are exact `Decimal` `Money` — never IEEE floats (price-dataset spec
|
|
4
|
+
* `prices/1`). `refresh()` is async here (JS is async-first; Python's sync `urllib` becomes `fetch`).
|
|
5
|
+
*/
|
|
6
|
+
import { Dec } from './decimal.js';
|
|
7
|
+
import { parseDecimalJson } from './json-decimal.js';
|
|
8
|
+
import { PRICES_JSON } from './prices-snapshot.js';
|
|
9
|
+
import { Money } from './types.js';
|
|
10
|
+
/** Raised when a model id is not present in the price table. */
|
|
11
|
+
export class UnknownModelError extends Error {
|
|
12
|
+
constructor(model) {
|
|
13
|
+
super(model);
|
|
14
|
+
this.name = 'UnknownModelError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
let table = null;
|
|
18
|
+
let sourceKind = 'bundled'; // "bundled" | "refreshed"
|
|
19
|
+
let sourceNameValue = 'bundled'; // "bundled" | "litellm" | "openrouter" | "azure" | "custom" | "default"
|
|
20
|
+
let sourceUrlValue = null;
|
|
21
|
+
/** Default static snapshot location used by `refresh()` when no url or source is given. */
|
|
22
|
+
export const SNAPSHOT_URL = 'https://raw.githubusercontent.com/cendorhq/cendor-libs/main/packages/cendor-core/src/cendor/core/prices.json';
|
|
23
|
+
export const LITELLM_URL = 'https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json';
|
|
24
|
+
export const OPENROUTER_URL = 'https://openrouter.ai/api/v1/models';
|
|
25
|
+
export const AZURE_URL = "https://prices.azure.com/api/retail/prices?api-version=2023-01-01-preview&$filter=productName eq 'Azure OpenAI'";
|
|
26
|
+
/** Optional explicit id aliases applied after prefix-stripping (extend as needed). */
|
|
27
|
+
const ALIASES = {};
|
|
28
|
+
function ensureLoaded() {
|
|
29
|
+
if (table === null) {
|
|
30
|
+
table = parseDecimalJson(PRICES_JSON);
|
|
31
|
+
}
|
|
32
|
+
return table;
|
|
33
|
+
}
|
|
34
|
+
function ratesFor(model) {
|
|
35
|
+
const models = ensureLoaded().models ?? {};
|
|
36
|
+
const r = models[model];
|
|
37
|
+
if (r === undefined)
|
|
38
|
+
throw new UnknownModelError(model);
|
|
39
|
+
return r;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Estimate the cost of a call from the price snapshot, as exact `Decimal` `Money`. `cachedTokens` is a
|
|
43
|
+
* subset of `inputTokens`, billed once: `input*(input−cached) + cached*cachedRate`. Unknown model
|
|
44
|
+
* throws {@link UnknownModelError}. Mirrors `cendor.core.prices.estimate`.
|
|
45
|
+
*/
|
|
46
|
+
export function estimate(model, inputTokens, opts = {}) {
|
|
47
|
+
const outputTokens = opts.outputTokens ?? 0;
|
|
48
|
+
const cachedTokens = opts.cachedTokens ?? 0;
|
|
49
|
+
const cacheWriteTokens = opts.cacheWriteTokens ?? 0;
|
|
50
|
+
const r = ratesFor(model);
|
|
51
|
+
const cached = Math.min(Math.max(cachedTokens, 0), inputTokens); // cached ⊆ input; clamp defensively
|
|
52
|
+
const inputRate = r.input;
|
|
53
|
+
if (inputRate === undefined)
|
|
54
|
+
throw new UnknownModelError(model);
|
|
55
|
+
const cachedRate = 'cached' in r ? r.cached : inputRate;
|
|
56
|
+
const writeRate = 'cache_write' in r ? r.cache_write : inputRate.times('1.25');
|
|
57
|
+
const outputRate = r.output ?? new Dec(0);
|
|
58
|
+
const amount = inputRate
|
|
59
|
+
.times(inputTokens - cached)
|
|
60
|
+
.plus(outputRate.times(outputTokens))
|
|
61
|
+
.plus(cachedRate.times(cached))
|
|
62
|
+
.plus(writeRate.times(Math.max(cacheWriteTokens, 0)));
|
|
63
|
+
return new Money(amount);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Register (or overwrite) a model's **per-token** rates in the active price table, so a model absent
|
|
67
|
+
* from the bundled snapshot (a custom/deployment/Hub id) is costed and USD budgets bind on it.
|
|
68
|
+
* Rates are exact `Decimal`. The higher-level `@cendor/sdk` `registerModelPrice` handles per-1M/1K
|
|
69
|
+
* unit conversion before calling this. Dropped by {@link _reset}.
|
|
70
|
+
*/
|
|
71
|
+
export function register(model, rates) {
|
|
72
|
+
const t = ensureLoaded();
|
|
73
|
+
if (!t.models)
|
|
74
|
+
t.models = {};
|
|
75
|
+
const r = { input: rates.input instanceof Dec ? rates.input : new Dec(rates.input) };
|
|
76
|
+
if (rates.output !== undefined)
|
|
77
|
+
r.output = rates.output instanceof Dec ? rates.output : new Dec(rates.output);
|
|
78
|
+
if (rates.cached !== undefined)
|
|
79
|
+
r.cached = rates.cached instanceof Dec ? rates.cached : new Dec(rates.cached);
|
|
80
|
+
if (rates.cache_write !== undefined) {
|
|
81
|
+
r.cache_write =
|
|
82
|
+
rates.cache_write instanceof Dec ? rates.cache_write : new Dec(rates.cache_write);
|
|
83
|
+
}
|
|
84
|
+
t.models[model] = r;
|
|
85
|
+
}
|
|
86
|
+
/** Sorted list of model ids known to the current price table. */
|
|
87
|
+
export function models() {
|
|
88
|
+
return Object.keys(ensureLoaded().models ?? {}).sort();
|
|
89
|
+
}
|
|
90
|
+
/** The `_updated` date of the loaded snapshot, or `null`. */
|
|
91
|
+
export function snapshotDate() {
|
|
92
|
+
return ensureLoaded()._updated ?? null;
|
|
93
|
+
}
|
|
94
|
+
/** `"bundled"` or `"refreshed"` — where the active table came from. */
|
|
95
|
+
export function source() {
|
|
96
|
+
return sourceKind;
|
|
97
|
+
}
|
|
98
|
+
/** Finer provenance: `"bundled"` | `"litellm"` | `"openrouter"` | `"azure"` | `"custom"` | `"default"`. */
|
|
99
|
+
export function sourceName() {
|
|
100
|
+
return sourceNameValue;
|
|
101
|
+
}
|
|
102
|
+
/** The URL the active table was fetched from, or `null` if it's the bundled snapshot. */
|
|
103
|
+
export function sourceUrl() {
|
|
104
|
+
return sourceUrlValue;
|
|
105
|
+
}
|
|
106
|
+
/** Age of the active table in days (today − `_updated`), or `null` if undatable. */
|
|
107
|
+
export function ageDays(today) {
|
|
108
|
+
const d = snapshotDate();
|
|
109
|
+
if (!d)
|
|
110
|
+
return null;
|
|
111
|
+
const parts = d.split('-').map((x) => Number.parseInt(x, 10));
|
|
112
|
+
if (parts.length !== 3 || parts.some((n) => Number.isNaN(n)))
|
|
113
|
+
return null;
|
|
114
|
+
const [y, m, dd] = parts;
|
|
115
|
+
const ref = today ?? new Date();
|
|
116
|
+
const refUtc = Date.UTC(ref.getUTCFullYear(), ref.getUTCMonth(), ref.getUTCDate());
|
|
117
|
+
const snapUtc = Date.UTC(y, m - 1, dd);
|
|
118
|
+
return Math.floor((refUtc - snapUtc) / 86_400_000);
|
|
119
|
+
}
|
|
120
|
+
/** `true` if the table is older than `maxAgeDays` (an undatable table is never stale). */
|
|
121
|
+
export function isStale(maxAgeDays = 30) {
|
|
122
|
+
const a = ageDays();
|
|
123
|
+
return a !== null && a > maxAgeDays;
|
|
124
|
+
}
|
|
125
|
+
// --------------------------------------------------------------------------- live-source adapters
|
|
126
|
+
function normalizeModelId(mid) {
|
|
127
|
+
let s = mid.trim();
|
|
128
|
+
if (s.includes('/'))
|
|
129
|
+
s = s.slice(s.indexOf('/') + 1);
|
|
130
|
+
s = s.toLowerCase();
|
|
131
|
+
return ALIASES[s] ?? s;
|
|
132
|
+
}
|
|
133
|
+
function dec(value) {
|
|
134
|
+
return value instanceof Dec ? value : new Dec(value);
|
|
135
|
+
}
|
|
136
|
+
function mapLitellm(raw) {
|
|
137
|
+
const out = {};
|
|
138
|
+
const obj = raw;
|
|
139
|
+
for (const [mid, rec] of Object.entries(obj)) {
|
|
140
|
+
if (rec === null || typeof rec !== 'object' || Array.isArray(rec))
|
|
141
|
+
continue;
|
|
142
|
+
const r = rec;
|
|
143
|
+
if (!('input_cost_per_token' in r))
|
|
144
|
+
continue;
|
|
145
|
+
const rates = { input: dec(r.input_cost_per_token) };
|
|
146
|
+
if (r.output_cost_per_token != null)
|
|
147
|
+
rates.output = dec(r.output_cost_per_token);
|
|
148
|
+
if (r.cache_read_input_token_cost != null)
|
|
149
|
+
rates.cached = dec(r.cache_read_input_token_cost);
|
|
150
|
+
out[normalizeModelId(mid)] = rates;
|
|
151
|
+
}
|
|
152
|
+
return { models: out };
|
|
153
|
+
}
|
|
154
|
+
function mapOpenrouter(raw) {
|
|
155
|
+
const out = {};
|
|
156
|
+
const data = raw.data;
|
|
157
|
+
if (!Array.isArray(data))
|
|
158
|
+
return { models: out };
|
|
159
|
+
for (const rec of data) {
|
|
160
|
+
if (rec === null || typeof rec !== 'object' || Array.isArray(rec))
|
|
161
|
+
continue;
|
|
162
|
+
const r = rec;
|
|
163
|
+
const mid = r.id;
|
|
164
|
+
const pricing = (r.pricing ?? {});
|
|
165
|
+
if (typeof mid !== 'string' || pricing.prompt == null)
|
|
166
|
+
continue;
|
|
167
|
+
const rates = { input: dec(pricing.prompt) };
|
|
168
|
+
if (pricing.completion != null)
|
|
169
|
+
rates.output = dec(pricing.completion);
|
|
170
|
+
const cached = pricing.input_cache_read;
|
|
171
|
+
if (cached != null && dec(cached).greaterThan(0))
|
|
172
|
+
rates.cached = dec(cached);
|
|
173
|
+
out[normalizeModelId(mid)] = rates;
|
|
174
|
+
}
|
|
175
|
+
return { models: out };
|
|
176
|
+
}
|
|
177
|
+
function azureUnitDivisor(unitOfMeasure) {
|
|
178
|
+
const u = (unitOfMeasure || '').toUpperCase().replace(/ /g, '');
|
|
179
|
+
if (u.includes('1M') || u.includes('1000000'))
|
|
180
|
+
return new Dec(1_000_000);
|
|
181
|
+
return new Dec(1000);
|
|
182
|
+
}
|
|
183
|
+
function mapAzure(raw) {
|
|
184
|
+
const byModel = {};
|
|
185
|
+
let latest = null;
|
|
186
|
+
const items = raw.Items;
|
|
187
|
+
if (!Array.isArray(items))
|
|
188
|
+
return { models: {} };
|
|
189
|
+
for (const item of items) {
|
|
190
|
+
if (item === null || typeof item !== 'object' || Array.isArray(item))
|
|
191
|
+
continue;
|
|
192
|
+
const it = item;
|
|
193
|
+
const eff = String(it.effectiveStartDate ?? '').slice(0, 10);
|
|
194
|
+
if (eff.length === 10 && (latest === null || eff > latest))
|
|
195
|
+
latest = eff;
|
|
196
|
+
const sku = String(it.skuName ?? '');
|
|
197
|
+
const low = sku.toLowerCase();
|
|
198
|
+
let direction;
|
|
199
|
+
if (low.includes('input') || low.includes(' inp') || low.endsWith('inp'))
|
|
200
|
+
direction = 'input';
|
|
201
|
+
else if (low.includes('output') || low.includes('outp'))
|
|
202
|
+
direction = 'output';
|
|
203
|
+
else
|
|
204
|
+
continue;
|
|
205
|
+
const meterName = String(it.meterName ?? '').toLowerCase();
|
|
206
|
+
if (!low.includes('global') && `${low} ${meterName}`.includes('regional'))
|
|
207
|
+
continue;
|
|
208
|
+
const price = it.retailPrice;
|
|
209
|
+
if (price == null)
|
|
210
|
+
continue;
|
|
211
|
+
const perToken = dec(price).dividedBy(azureUnitDivisor(String(it.unitOfMeasure ?? '')));
|
|
212
|
+
let head = low;
|
|
213
|
+
for (const cut of [' inp', ' input', ' outp', ' output']) {
|
|
214
|
+
if (head.includes(cut)) {
|
|
215
|
+
head = head.slice(0, head.indexOf(cut));
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const words = head.trim().split(/\s+/).filter(Boolean);
|
|
220
|
+
while (words.length &&
|
|
221
|
+
/^\d+$/.test(words[words.length - 1]) &&
|
|
222
|
+
[3, 4].includes(words[words.length - 1].length)) {
|
|
223
|
+
words.pop();
|
|
224
|
+
}
|
|
225
|
+
const mid = normalizeModelId(words.join('-'));
|
|
226
|
+
let rates = byModel[mid];
|
|
227
|
+
if (rates === undefined) {
|
|
228
|
+
rates = {};
|
|
229
|
+
byModel[mid] = rates;
|
|
230
|
+
}
|
|
231
|
+
const existing = rates[direction];
|
|
232
|
+
if (existing === undefined || perToken.lessThan(existing))
|
|
233
|
+
rates[direction] = perToken;
|
|
234
|
+
}
|
|
235
|
+
const out = {};
|
|
236
|
+
for (const [mid, r] of Object.entries(byModel))
|
|
237
|
+
if ('input' in r)
|
|
238
|
+
out[mid] = r;
|
|
239
|
+
const result = { models: out };
|
|
240
|
+
if (latest !== null)
|
|
241
|
+
result._updated = latest;
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
const SOURCES = {
|
|
245
|
+
litellm: [LITELLM_URL, mapLitellm],
|
|
246
|
+
openrouter: [OPENROUTER_URL, mapOpenrouter],
|
|
247
|
+
azure: [AZURE_URL, mapAzure],
|
|
248
|
+
};
|
|
249
|
+
/** Names of the built-in live price sources accepted by `refresh({ source })`. */
|
|
250
|
+
export function sources() {
|
|
251
|
+
return Object.keys(SOURCES).sort();
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Replace the table from a live source or static JSON URL. Never throws; offline-safe. Returns `true`
|
|
255
|
+
* if the table was updated, `false` if the fetch/parse/map failed (the current table stays active).
|
|
256
|
+
* Mirrors `cendor.core.prices.refresh` (async here: uses `fetch`).
|
|
257
|
+
*/
|
|
258
|
+
export async function refresh(url, opts = {}) {
|
|
259
|
+
const { source: sourceArg, mapper, timeout = 5.0 } = opts;
|
|
260
|
+
let target;
|
|
261
|
+
let adapter;
|
|
262
|
+
let name;
|
|
263
|
+
if (sourceArg != null) {
|
|
264
|
+
const entry = SOURCES[sourceArg];
|
|
265
|
+
if (entry === undefined)
|
|
266
|
+
return false;
|
|
267
|
+
target = entry[0];
|
|
268
|
+
adapter = mapper ?? entry[1];
|
|
269
|
+
name = sourceArg;
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
target = url || SNAPSHOT_URL;
|
|
273
|
+
adapter = mapper;
|
|
274
|
+
name = url ? 'custom' : 'default';
|
|
275
|
+
}
|
|
276
|
+
if (!target || !/^https?:\/\//i.test(target))
|
|
277
|
+
return false;
|
|
278
|
+
try {
|
|
279
|
+
const controller = new AbortController();
|
|
280
|
+
const timer = setTimeout(() => controller.abort(), timeout * 1000);
|
|
281
|
+
let text;
|
|
282
|
+
try {
|
|
283
|
+
const resp = await fetch(target, { signal: controller.signal });
|
|
284
|
+
text = await resp.text();
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
clearTimeout(timer);
|
|
288
|
+
}
|
|
289
|
+
const raw = parseDecimalJson(text);
|
|
290
|
+
const data = adapter ? adapter(raw) : raw;
|
|
291
|
+
if (data && typeof data === 'object' && data.models && Object.keys(data.models).length > 0) {
|
|
292
|
+
table = data;
|
|
293
|
+
sourceKind = 'refreshed';
|
|
294
|
+
sourceNameValue = name;
|
|
295
|
+
sourceUrlValue = target;
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
/** Test helper: drop the loaded table so the bundled snapshot reloads. */
|
|
305
|
+
export function _reset() {
|
|
306
|
+
table = null;
|
|
307
|
+
sourceKind = 'bundled';
|
|
308
|
+
sourceNameValue = 'bundled';
|
|
309
|
+
sourceUrlValue = null;
|
|
310
|
+
}
|
|
311
|
+
//# sourceMappingURL=prices.js.map
|