@kunal_dutt/token-lens 0.2.17 → 0.2.19
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/GATEWAY.md +113 -5
- package/README.md +15 -4
- package/check-agent.mjs +5 -5
- package/gateway.mjs +66 -63
- package/package.json +1 -1
- package/sdk.mjs +461 -93
- package/server.mjs +27 -27
- package/token-lens.mcpb +0 -0
- package/types/src/gateway/config.d.ts +29 -1
- package/types/src/gateway/customers.d.ts +168 -0
- package/types/src/gateway/metrics.d.ts +2 -1
- package/types/src/gateway/runs.d.ts +6 -3
- package/types/src/gateway/shared.d.ts +5 -0
- package/types/src/gateway/storage.d.ts +7 -0
- package/types/src/sdk/index.d.ts +51 -0
package/sdk.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/sdk/index.ts
|
|
2
|
-
import { createHash as
|
|
2
|
+
import { createHash as createHash4, randomUUID } from "node:crypto";
|
|
3
3
|
|
|
4
4
|
// src/core/advice.ts
|
|
5
5
|
var fmt = (n) => n.toLocaleString("en-US");
|
|
@@ -203,7 +203,7 @@ var BytePairEncodingCore = class {
|
|
|
203
203
|
});
|
|
204
204
|
this.bytePairNonUtfSortedEncoder = binaryLookup.sort((a, b) => compareUint8Arrays(a[0], b[0]));
|
|
205
205
|
this.specialTokensEncoder = specialTokensEncoder ?? /* @__PURE__ */ new Map();
|
|
206
|
-
this.specialTokensDecoder = specialTokensEncoder ? new Map([...specialTokensEncoder].map(([
|
|
206
|
+
this.specialTokensDecoder = specialTokensEncoder ? new Map([...specialTokensEncoder].map(([key2, value]) => [value, key2])) : /* @__PURE__ */ new Map();
|
|
207
207
|
this.tokenSplitRegex = tokenSplitRegex;
|
|
208
208
|
const allSpecialTokensRegex = [...this.specialTokensEncoder.keys()].map(escapeRegExp).join("|");
|
|
209
209
|
try {
|
|
@@ -336,38 +336,38 @@ var BytePairEncodingCore = class {
|
|
|
336
336
|
if (tokenBytesOrString) yield tokenBytesOrString;
|
|
337
337
|
}
|
|
338
338
|
}
|
|
339
|
-
getBpeRankFromString(
|
|
340
|
-
return this.bytePairStringRankEncoder.get(
|
|
339
|
+
getBpeRankFromString(key2) {
|
|
340
|
+
return this.bytePairStringRankEncoder.get(key2);
|
|
341
341
|
}
|
|
342
|
-
getBpeRankFromStringOrThrow(
|
|
343
|
-
const value = this.getBpeRankFromString(
|
|
344
|
-
if (value === void 0) throw new Error(`The byte-pair encoding does not contain a value for: ${
|
|
342
|
+
getBpeRankFromStringOrThrow(key2) {
|
|
343
|
+
const value = this.getBpeRankFromString(key2);
|
|
344
|
+
if (value === void 0) throw new Error(`The byte-pair encoding does not contain a value for: ${key2}`);
|
|
345
345
|
return value;
|
|
346
346
|
}
|
|
347
|
-
getBpeRankFromBytes(
|
|
348
|
-
const keyAsString = tryConvertToString(
|
|
347
|
+
getBpeRankFromBytes(key2) {
|
|
348
|
+
const keyAsString = tryConvertToString(key2);
|
|
349
349
|
if (keyAsString !== void 0) return this.getBpeRankFromString(keyAsString);
|
|
350
|
-
const index = this.binarySearch(
|
|
350
|
+
const index = this.binarySearch(key2);
|
|
351
351
|
if (index !== -1) return this.bytePairNonUtfSortedEncoder[index][1];
|
|
352
352
|
}
|
|
353
|
-
getBpeRankFromBytesOrThrow(
|
|
354
|
-
const value = this.getBpeRankFromBytes(
|
|
355
|
-
if (value === void 0) throw new Error(`The byte-pair encoding does not contain a value for: ${
|
|
353
|
+
getBpeRankFromBytesOrThrow(key2) {
|
|
354
|
+
const value = this.getBpeRankFromBytes(key2);
|
|
355
|
+
if (value === void 0) throw new Error(`The byte-pair encoding does not contain a value for: ${key2.toString()}`);
|
|
356
356
|
return value;
|
|
357
357
|
}
|
|
358
|
-
binarySearch(
|
|
358
|
+
binarySearch(key2) {
|
|
359
359
|
let low = 0;
|
|
360
360
|
let high = this.bytePairNonUtfSortedEncoder.length - 1;
|
|
361
361
|
while (low <= high) {
|
|
362
362
|
const mid = low + high >>> 1;
|
|
363
363
|
const midKey = this.bytePairNonUtfSortedEncoder[mid][0];
|
|
364
364
|
let cmp = 0;
|
|
365
|
-
const maxLength = Math.min(midKey.length,
|
|
365
|
+
const maxLength = Math.min(midKey.length, key2.length);
|
|
366
366
|
for (let i = 0; i < maxLength; i++) {
|
|
367
|
-
cmp = midKey[i] -
|
|
367
|
+
cmp = midKey[i] - key2[i];
|
|
368
368
|
if (cmp !== 0) break;
|
|
369
369
|
}
|
|
370
|
-
if (cmp === 0) cmp = midKey.length -
|
|
370
|
+
if (cmp === 0) cmp = midKey.length - key2.length;
|
|
371
371
|
if (cmp === 0) return mid;
|
|
372
372
|
if (cmp < 0) low = mid + 1;
|
|
373
373
|
else high = mid - 1;
|
|
@@ -394,13 +394,13 @@ var BytePairEncodingCore = class {
|
|
|
394
394
|
}
|
|
395
395
|
return this.specialTokensDecoder.get(tokenRank);
|
|
396
396
|
}
|
|
397
|
-
addToMergeCache(
|
|
397
|
+
addToMergeCache(key2, value) {
|
|
398
398
|
if (!this.mergeCache) return;
|
|
399
399
|
if (this.mergeCache.size >= this.mergeCacheSize) {
|
|
400
400
|
const firstKey = this.mergeCache.keys().next().value;
|
|
401
401
|
this.mergeCache.delete(firstKey);
|
|
402
402
|
}
|
|
403
|
-
this.mergeCache.set(
|
|
403
|
+
this.mergeCache.set(key2, value);
|
|
404
404
|
}
|
|
405
405
|
bytePairEncode(input) {
|
|
406
406
|
if (input.length === 1 && isAscii(input.codePointAt(0))) return [this.getBpeRankFromStringOrThrow(input)];
|
|
@@ -420,8 +420,8 @@ var BytePairEncodingCore = class {
|
|
|
420
420
|
const ranks = [];
|
|
421
421
|
const getRank = (startIndex, pairStart = starts[startIndex], pairEnd = starts[startIndex + 2]) => {
|
|
422
422
|
if (pairEnd === void 0) return Number.POSITIVE_INFINITY;
|
|
423
|
-
const
|
|
424
|
-
return this.getBpeRankFromBytes(
|
|
423
|
+
const key2 = piece.subarray(pairStart, pairEnd);
|
|
424
|
+
return this.getBpeRankFromBytes(key2) ?? Number.POSITIVE_INFINITY;
|
|
425
425
|
};
|
|
426
426
|
for (let i = 0; i <= piece.length; i++) {
|
|
427
427
|
starts.push(i);
|
|
@@ -206098,8 +206098,8 @@ function counts(text) {
|
|
|
206098
206098
|
const m = /* @__PURE__ */ new Map();
|
|
206099
206099
|
for (const [kind, re] of ANCHORS) {
|
|
206100
206100
|
for (const hit of text.match(re) ?? []) {
|
|
206101
|
-
const
|
|
206102
|
-
m.set(
|
|
206101
|
+
const key2 = `${kind}:${hit.toLowerCase()}`;
|
|
206102
|
+
m.set(key2, (m.get(key2) ?? 0) + 1);
|
|
206103
206103
|
}
|
|
206104
206104
|
}
|
|
206105
206105
|
return m;
|
|
@@ -206108,7 +206108,7 @@ function checkMeaning(original, rewritten, mode = "count") {
|
|
|
206108
206108
|
const before = counts(original);
|
|
206109
206109
|
const after = counts(rewritten);
|
|
206110
206110
|
const lost = [];
|
|
206111
|
-
for (const [
|
|
206111
|
+
for (const [key2, n] of before) if ((after.get(key2) ?? 0) < (mode === "count" ? n : 1)) lost.push(key2);
|
|
206112
206112
|
return { ok: lost.length === 0, lost };
|
|
206113
206113
|
}
|
|
206114
206114
|
var DROPPABLE = new Set(
|
|
@@ -206207,13 +206207,13 @@ function dedupeSentences(text, applied) {
|
|
|
206207
206207
|
const seen = /* @__PURE__ */ new Set();
|
|
206208
206208
|
return text.split("\n").map(
|
|
206209
206209
|
(line) => line.split(/(?<=[.!?])\s+/).filter((sentence) => {
|
|
206210
|
-
const
|
|
206211
|
-
if (
|
|
206212
|
-
if (seen.has(
|
|
206210
|
+
const key2 = sentence.replace(/\W+/g, " ").trim().toLowerCase();
|
|
206211
|
+
if (key2.split(" ").length < 4) return true;
|
|
206212
|
+
if (seen.has(key2)) {
|
|
206213
206213
|
applied.push("repeated sentence");
|
|
206214
206214
|
return false;
|
|
206215
206215
|
}
|
|
206216
|
-
seen.add(
|
|
206216
|
+
seen.add(key2);
|
|
206217
206217
|
return true;
|
|
206218
206218
|
}).join(" ")
|
|
206219
206219
|
).join("\n");
|
|
@@ -206261,11 +206261,11 @@ function trimLog(text, level, applied) {
|
|
|
206261
206261
|
i++;
|
|
206262
206262
|
}
|
|
206263
206263
|
}
|
|
206264
|
-
const
|
|
206264
|
+
const key2 = level === "aggressive" ? normalizeLogLine : (l) => l;
|
|
206265
206265
|
const result = [];
|
|
206266
206266
|
for (let i = 0; i < out.length; ) {
|
|
206267
206267
|
let j = i + 1;
|
|
206268
|
-
while (j < out.length && out[i].trim() &&
|
|
206268
|
+
while (j < out.length && out[i].trim() && key2(out[j]) === key2(out[i])) j++;
|
|
206269
206269
|
result.push(out[i]);
|
|
206270
206270
|
if (j - i >= 3) {
|
|
206271
206271
|
result.push(`... same line repeated ${j - i - 1} more times`);
|
|
@@ -206480,6 +206480,90 @@ var PROFILES = {
|
|
|
206480
206480
|
reasoning: false,
|
|
206481
206481
|
pricing: { inputPerMTok: 2, outputPerMTok: 6 }
|
|
206482
206482
|
}),
|
|
206483
|
+
// Open-weight families, as served by API hosts (Groq, Together, Fireworks, OpenRouter, their makers' own
|
|
206484
|
+
// APIs) or run locally (Ollama, LM Studio, vLLM). Hosted prices differ by host: these are typical list
|
|
206485
|
+
// prices, and running locally costs no tokens, so set your own price in settings or registerModel.
|
|
206486
|
+
llama: P({
|
|
206487
|
+
id: "llama",
|
|
206488
|
+
label: "Llama",
|
|
206489
|
+
family: "other",
|
|
206490
|
+
contextWindow: 128e3,
|
|
206491
|
+
tokenizer: "heuristic",
|
|
206492
|
+
charsPerToken: 3.9,
|
|
206493
|
+
reasoning: false,
|
|
206494
|
+
pricing: { inputPerMTok: 0.27, outputPerMTok: 0.85 }
|
|
206495
|
+
}),
|
|
206496
|
+
qwen: P({
|
|
206497
|
+
id: "qwen",
|
|
206498
|
+
label: "Qwen",
|
|
206499
|
+
family: "other",
|
|
206500
|
+
contextWindow: 128e3,
|
|
206501
|
+
tokenizer: "heuristic",
|
|
206502
|
+
charsPerToken: 3.8,
|
|
206503
|
+
reasoning: false,
|
|
206504
|
+
pricing: { inputPerMTok: 0.4, outputPerMTok: 1.2 }
|
|
206505
|
+
}),
|
|
206506
|
+
kimi: P({
|
|
206507
|
+
id: "kimi",
|
|
206508
|
+
label: "Kimi",
|
|
206509
|
+
family: "other",
|
|
206510
|
+
contextWindow: 256e3,
|
|
206511
|
+
tokenizer: "heuristic",
|
|
206512
|
+
charsPerToken: 3.8,
|
|
206513
|
+
reasoning: false,
|
|
206514
|
+
pricing: { inputPerMTok: 0.6, outputPerMTok: 2.5, cachedInputPerMTok: 0.15 }
|
|
206515
|
+
}),
|
|
206516
|
+
glm: P({
|
|
206517
|
+
id: "glm",
|
|
206518
|
+
label: "GLM",
|
|
206519
|
+
family: "other",
|
|
206520
|
+
contextWindow: 2e5,
|
|
206521
|
+
tokenizer: "heuristic",
|
|
206522
|
+
charsPerToken: 3.8,
|
|
206523
|
+
reasoning: false,
|
|
206524
|
+
pricing: { inputPerMTok: 0.6, outputPerMTok: 2.2, cachedInputPerMTok: 0.11 }
|
|
206525
|
+
}),
|
|
206526
|
+
command: P({
|
|
206527
|
+
id: "command",
|
|
206528
|
+
label: "Cohere Command",
|
|
206529
|
+
family: "other",
|
|
206530
|
+
contextWindow: 256e3,
|
|
206531
|
+
tokenizer: "heuristic",
|
|
206532
|
+
charsPerToken: 4,
|
|
206533
|
+
reasoning: false,
|
|
206534
|
+
pricing: { inputPerMTok: 2.5, outputPerMTok: 10 }
|
|
206535
|
+
}),
|
|
206536
|
+
sonar: P({
|
|
206537
|
+
id: "sonar",
|
|
206538
|
+
label: "Perplexity Sonar",
|
|
206539
|
+
family: "other",
|
|
206540
|
+
contextWindow: 128e3,
|
|
206541
|
+
tokenizer: "heuristic",
|
|
206542
|
+
charsPerToken: 4,
|
|
206543
|
+
reasoning: false,
|
|
206544
|
+
pricing: { inputPerMTok: 1, outputPerMTok: 1 }
|
|
206545
|
+
}),
|
|
206546
|
+
// Small open models, mostly run locally (where tokens cost nothing): priced at a typical host's rate.
|
|
206547
|
+
gemma: P({
|
|
206548
|
+
id: "gemma",
|
|
206549
|
+
label: "Gemma",
|
|
206550
|
+
family: "other",
|
|
206551
|
+
contextWindow: 128e3,
|
|
206552
|
+
tokenizer: "heuristic",
|
|
206553
|
+
charsPerToken: 3.9,
|
|
206554
|
+
reasoning: false,
|
|
206555
|
+
pricing: { inputPerMTok: 0.1, outputPerMTok: 0.2 }
|
|
206556
|
+
}),
|
|
206557
|
+
phi: P({
|
|
206558
|
+
id: "phi",
|
|
206559
|
+
label: "Phi",
|
|
206560
|
+
family: "other",
|
|
206561
|
+
contextWindow: 16e3,
|
|
206562
|
+
tokenizer: "heuristic",
|
|
206563
|
+
charsPerToken: 3.9,
|
|
206564
|
+
reasoning: false,
|
|
206565
|
+
pricing: { inputPerMTok: 0.07, outputPerMTok: 0.14 }
|
|
206566
|
+
}),
|
|
206483
206567
|
generic: P({
|
|
206484
206568
|
id: "generic",
|
|
206485
206569
|
label: "Unknown model",
|
|
@@ -206497,12 +206581,22 @@ var RULES2 = [
|
|
|
206497
206581
|
[/\bclaude\b.*\bopus\b|\bopus\b/, "claude-opus"],
|
|
206498
206582
|
[/\bsonnet\b/, "claude-sonnet"],
|
|
206499
206583
|
[/\bhaiku\b/, "claude-haiku"],
|
|
206584
|
+
// Open models whose names can carry "flash" or "pro" come before the Gemini rules.
|
|
206585
|
+
[/\b(qwen|qwq)/, "qwen"],
|
|
206586
|
+
[/\b(glm|chatglm|zhipu)\b|\bglm-?\d/, "glm"],
|
|
206587
|
+
[/\b(kimi|moonshot)/, "kimi"],
|
|
206588
|
+
[/\bgemma/, "gemma"],
|
|
206500
206589
|
[/\bgemini\b.*\bflash\b|\bflash\b/, "gemini-flash"],
|
|
206501
206590
|
[/\bgemini\b.*\b(pro|ultra|deep think)\b|^\s*\d(\.\d)?\s*pro\b|\bdeep think\b/, "gemini-pro"],
|
|
206502
206591
|
[/\bdeepseek\b.*\b(r1|reason|think|deepthink)|\bdeepthink\b/, "deepseek-reasoning"],
|
|
206503
206592
|
[/\bdeepseek\b/, "deepseek"],
|
|
206504
206593
|
[/\b(mistral|le chat|magistral|codestral)\b/, "mistral"],
|
|
206505
206594
|
[/\bgrok\b/, "grok"],
|
|
206595
|
+
// After DeepSeek, so "deepseek-r1-distill-llama" stays DeepSeek.
|
|
206596
|
+
[/\bllama|codellama/, "llama"],
|
|
206597
|
+
[/\bcommand[- ]?(r|a)\b|\bcohere\b/, "command"],
|
|
206598
|
+
[/\bsonar\b|\bperplexity\b/, "sonar"],
|
|
206599
|
+
[/\bphi-?\d/, "phi"],
|
|
206506
206600
|
[/\bo[1-9](-mini|-pro)?\b|\bgpt[- ]?\S*\s*(thinking|pro)\b|\bthinking\b/, "gpt-reasoning"],
|
|
206507
206601
|
[/\b(gpt|chatgpt)\b|\bgpt-?\d/, "gpt"],
|
|
206508
206602
|
[/\bgemini\b/, "gemini-pro"],
|
|
@@ -206561,33 +206655,8 @@ function detectProfile(hint, siteId = "generic") {
|
|
|
206561
206655
|
return PROFILES[SITE_DEFAULTS[siteId] ?? "generic"];
|
|
206562
206656
|
}
|
|
206563
206657
|
|
|
206564
|
-
// src/gateway/
|
|
206565
|
-
|
|
206566
|
-
port: 8080,
|
|
206567
|
-
host: "0.0.0.0",
|
|
206568
|
-
mode: "optimize",
|
|
206569
|
-
level: "auto",
|
|
206570
|
-
safe: { prompt: true, minifyJson: true, collapseRepeats: true, dedupeToolOutputs: true, dedupePassages: true, dedupeImages: true, promptCaching: true, dedupeRetries: true, cacheEmbeddings: true },
|
|
206571
|
-
optIn: { elideOldToolOutputs: false, lazyTools: false, trimToolFields: false, compactJson: false, parkToolOutputs: false, flex: false, warmCache: false },
|
|
206572
|
-
verify: { sampleRate: 0.05, minSimilarity: 0.8, maxShareOfSavings: 0.2, freeChecks: 5, probation: 5 },
|
|
206573
|
-
runBudget: {},
|
|
206574
|
-
batch: { flushSeconds: 60, pollSeconds: 60, maxRequests: 1e3, discount: 0.5 },
|
|
206575
|
-
flexDiscount: 0.5,
|
|
206576
|
-
embeddingsCacheMB: 256,
|
|
206577
|
-
// Models carry their own long-context price (src/core/models.ts); entries here override or add to them.
|
|
206578
|
-
priceTiers: [],
|
|
206579
|
-
storage: { keyPrefix: "token-lens:", flushSeconds: 10, runDays: 7 },
|
|
206580
|
-
retryWindowSeconds: 120,
|
|
206581
|
-
blockOverContext: false,
|
|
206582
|
-
upstreams: {},
|
|
206583
|
-
routes: [],
|
|
206584
|
-
models: [],
|
|
206585
|
-
clientKeys: [],
|
|
206586
|
-
maxBodyBytes: 32 * 1024 * 1024,
|
|
206587
|
-
maxRuns: 2e3,
|
|
206588
|
-
logRequests: true,
|
|
206589
|
-
reportToolArgs: false
|
|
206590
|
-
};
|
|
206658
|
+
// src/gateway/customers.ts
|
|
206659
|
+
import { createHash } from "node:crypto";
|
|
206591
206660
|
|
|
206592
206661
|
// src/gateway/metrics.ts
|
|
206593
206662
|
var emptyTotals = () => ({
|
|
@@ -206671,12 +206740,260 @@ function view(t) {
|
|
|
206671
206740
|
};
|
|
206672
206741
|
}
|
|
206673
206742
|
|
|
206743
|
+
// src/gateway/customers.ts
|
|
206744
|
+
var CUSTOMER_DEFAULTS = {
|
|
206745
|
+
fromBody: false,
|
|
206746
|
+
hashIds: false,
|
|
206747
|
+
maxCustomers: 1e5,
|
|
206748
|
+
budget: {},
|
|
206749
|
+
plans: {},
|
|
206750
|
+
teamBudgets: {}
|
|
206751
|
+
};
|
|
206752
|
+
function periodKey(period, now) {
|
|
206753
|
+
const iso = now.toISOString();
|
|
206754
|
+
return period === "day" ? `d${iso.slice(0, 10)}` : `m${iso.slice(0, 7)}`;
|
|
206755
|
+
}
|
|
206756
|
+
var hashId = (id) => `h:${createHash("sha256").update(id).digest("hex").slice(0, 16)}`;
|
|
206757
|
+
function clean(id) {
|
|
206758
|
+
if (typeof id !== "string") return void 0;
|
|
206759
|
+
const s = id.replace(/[\u0000-\u001f\u007f]/g, "").trim().slice(0, 128);
|
|
206760
|
+
return s || void 0;
|
|
206761
|
+
}
|
|
206762
|
+
function customerOf(headerValue, body, cfg) {
|
|
206763
|
+
const raw = clean(headerValue) ?? (cfg.fromBody ? clean(body?.safety_identifier) ?? clean(body?.user) ?? clean(body?.metadata?.user_id) : void 0);
|
|
206764
|
+
if (!raw) return void 0;
|
|
206765
|
+
return cfg.hashIds ? hashId(raw) : raw;
|
|
206766
|
+
}
|
|
206767
|
+
var storedId = (id, cfg) => cfg.hashIds ? hashId(id) : id;
|
|
206768
|
+
var LIMITS = [
|
|
206769
|
+
["dailyUsd", "day", "usd"],
|
|
206770
|
+
["monthlyUsd", "month", "usd"],
|
|
206771
|
+
["dailyTokens", "day", "tokens"],
|
|
206772
|
+
["monthlyTokens", "month", "tokens"]
|
|
206773
|
+
];
|
|
206774
|
+
var HEADER_LIMITS = {
|
|
206775
|
+
"daily-usd": "dailyUsd",
|
|
206776
|
+
"monthly-usd": "monthlyUsd",
|
|
206777
|
+
"daily-tokens": "dailyTokens",
|
|
206778
|
+
"monthly-tokens": "monthlyTokens"
|
|
206779
|
+
};
|
|
206780
|
+
var isLimit = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
206781
|
+
function cleanBudget(b) {
|
|
206782
|
+
const out = {};
|
|
206783
|
+
for (const [k] of LIMITS) if (isLimit(b?.[k])) out[k] = b[k];
|
|
206784
|
+
return out;
|
|
206785
|
+
}
|
|
206786
|
+
var UnknownPlanError = class extends Error {
|
|
206787
|
+
};
|
|
206788
|
+
function customerBudget(cfg, plan, header) {
|
|
206789
|
+
if (plan && !cfg.plans[plan]) throw new UnknownPlanError(`Unknown plan "${plan}". Plans in this gateway's config: ${Object.keys(cfg.plans).join(", ") || "none"}.`);
|
|
206790
|
+
const fromHeaders = {};
|
|
206791
|
+
for (const [suffix, key2] of Object.entries(HEADER_LIMITS)) {
|
|
206792
|
+
const v = header(`x-token-lens-customer-${suffix}`);
|
|
206793
|
+
if (v !== void 0 && v !== "") fromHeaders[key2] = Number(v);
|
|
206794
|
+
}
|
|
206795
|
+
return { ...cleanBudget(cfg.budget), ...cleanBudget(plan ? cfg.plans[plan] : void 0), ...cleanBudget(fromHeaders) };
|
|
206796
|
+
}
|
|
206797
|
+
var hasLimits = (b) => LIMITS.some(([k]) => b[k] !== void 0);
|
|
206798
|
+
var TEAM = "";
|
|
206799
|
+
var key = (...parts) => parts.join("\0");
|
|
206800
|
+
var CustomerLedger = class {
|
|
206801
|
+
constructor(maxCustomers) {
|
|
206802
|
+
this.maxCustomers = maxCustomers;
|
|
206803
|
+
}
|
|
206804
|
+
maxCustomers;
|
|
206805
|
+
/** key(app, customer) -> totals. Map order is least recently seen first. */
|
|
206806
|
+
rows = /* @__PURE__ */ new Map();
|
|
206807
|
+
/** key(period, app, customer | TEAM) -> spend recorded by this copy. */
|
|
206808
|
+
local = /* @__PURE__ */ new Map();
|
|
206809
|
+
/** What of `local` was already written to shared storage. */
|
|
206810
|
+
flushed = /* @__PURE__ */ new Map();
|
|
206811
|
+
/** Spend of every copy, as last read from shared storage (includes what this copy flushed). */
|
|
206812
|
+
shared = /* @__PURE__ */ new Map();
|
|
206813
|
+
periods = "";
|
|
206814
|
+
prune(now) {
|
|
206815
|
+
const day = periodKey("day", now);
|
|
206816
|
+
const month = periodKey("month", now);
|
|
206817
|
+
const current = day + month;
|
|
206818
|
+
if (current !== this.periods) {
|
|
206819
|
+
this.periods = current;
|
|
206820
|
+
for (const m of [this.local, this.flushed, this.shared])
|
|
206821
|
+
for (const k of m.keys()) {
|
|
206822
|
+
const p = k.slice(0, k.indexOf("\0"));
|
|
206823
|
+
if (p !== day && p !== month) m.delete(k);
|
|
206824
|
+
}
|
|
206825
|
+
}
|
|
206826
|
+
return [day, month];
|
|
206827
|
+
}
|
|
206828
|
+
/** One call: the same figures as the savings report, for the customer and its team. */
|
|
206829
|
+
record(app, customer, m, now = /* @__PURE__ */ new Date()) {
|
|
206830
|
+
const t = emptyTotals();
|
|
206831
|
+
addCall(t, m);
|
|
206832
|
+
if (customer) {
|
|
206833
|
+
const k = key(app, customer);
|
|
206834
|
+
let row = this.rows.get(k);
|
|
206835
|
+
if (row) this.rows.delete(k);
|
|
206836
|
+
else row = { totals: emptyTotals(), firstSeen: now.toISOString(), lastSeen: "" };
|
|
206837
|
+
row.lastSeen = now.toISOString();
|
|
206838
|
+
for (const f of Object.keys(t)) row.totals[f] += t[f];
|
|
206839
|
+
this.rows.set(k, row);
|
|
206840
|
+
while (this.rows.size > this.maxCustomers) this.rows.delete(this.rows.keys().next().value);
|
|
206841
|
+
}
|
|
206842
|
+
if (m.status >= 400 || m.replayed) return;
|
|
206843
|
+
const spend = { usd: t.costUsd, tokens: t.inputTokens + t.outputTokens };
|
|
206844
|
+
if (!spend.usd && !spend.tokens) return;
|
|
206845
|
+
for (const p of this.prune(now))
|
|
206846
|
+
for (const who of customer ? [customer, TEAM] : [TEAM]) {
|
|
206847
|
+
const k = key(p, app, who);
|
|
206848
|
+
const s = this.local.get(k) ?? { usd: 0, tokens: 0 };
|
|
206849
|
+
s.usd += spend.usd;
|
|
206850
|
+
s.tokens += spend.tokens;
|
|
206851
|
+
this.local.set(k, s);
|
|
206852
|
+
}
|
|
206853
|
+
}
|
|
206854
|
+
/** Spend in the current day or month, across every copy when shared storage is on. */
|
|
206855
|
+
spent(app, customer, period, now = /* @__PURE__ */ new Date()) {
|
|
206856
|
+
const [day, month] = this.prune(now);
|
|
206857
|
+
const k = key(period === "day" ? day : month, app, customer ?? TEAM);
|
|
206858
|
+
const l = this.local.get(k);
|
|
206859
|
+
const f = this.flushed.get(k);
|
|
206860
|
+
const s = this.shared.get(k);
|
|
206861
|
+
return {
|
|
206862
|
+
usd: (s?.usd ?? 0) + (l?.usd ?? 0) - (f?.usd ?? 0),
|
|
206863
|
+
tokens: (s?.tokens ?? 0) + (l?.tokens ?? 0) - (f?.tokens ?? 0)
|
|
206864
|
+
};
|
|
206865
|
+
}
|
|
206866
|
+
/** The first limit already reached, the customer's checked before the team's. */
|
|
206867
|
+
over(app, customer, budgets, now = /* @__PURE__ */ new Date()) {
|
|
206868
|
+
const checks = [["team", budgets.team, void 0]];
|
|
206869
|
+
if (customer) checks.unshift(["customer", budgets.customer, customer]);
|
|
206870
|
+
for (const [who, budget, id] of checks)
|
|
206871
|
+
for (const [limit, period, field] of LIMITS) {
|
|
206872
|
+
const cap = budget[limit];
|
|
206873
|
+
if (cap === void 0) continue;
|
|
206874
|
+
const spent = this.spent(app, id, period, now)[field];
|
|
206875
|
+
if (spent >= cap) return { who, limit, cap, spent };
|
|
206876
|
+
}
|
|
206877
|
+
return void 0;
|
|
206878
|
+
}
|
|
206879
|
+
/** What is left under the tightest limits that apply, for the response headers. */
|
|
206880
|
+
remaining(app, customer, budgets, now = /* @__PURE__ */ new Date()) {
|
|
206881
|
+
const out = {};
|
|
206882
|
+
const pairs = [[budgets.team, void 0]];
|
|
206883
|
+
if (customer) pairs.push([budgets.customer, customer]);
|
|
206884
|
+
for (const [budget, id] of pairs)
|
|
206885
|
+
for (const [limit, period, field] of LIMITS) {
|
|
206886
|
+
const cap = budget[limit];
|
|
206887
|
+
if (cap === void 0) continue;
|
|
206888
|
+
const left = Math.max(0, cap - this.spent(app, id, period, now)[field]);
|
|
206889
|
+
out[field] = Math.min(out[field] ?? Infinity, left);
|
|
206890
|
+
}
|
|
206891
|
+
return out;
|
|
206892
|
+
}
|
|
206893
|
+
// ---------------------------------------------------------- reports
|
|
206894
|
+
/** Raw totals rows (key: team and customer), for shared storage. */
|
|
206895
|
+
snapshot() {
|
|
206896
|
+
return new Map([...this.rows].map(([k, r]) => [k, { ...r.totals }]));
|
|
206897
|
+
}
|
|
206898
|
+
lastSeen(app, customer) {
|
|
206899
|
+
return this.rows.get(key(app, customer))?.lastSeen;
|
|
206900
|
+
}
|
|
206901
|
+
/**
|
|
206902
|
+
* One row per customer with savings, spend today and this month; `rows` from
|
|
206903
|
+
* shared storage when given, else this copy's own. `app` limits it to a team.
|
|
206904
|
+
*/
|
|
206905
|
+
list(app, rows = this.snapshot(), now = /* @__PURE__ */ new Date()) {
|
|
206906
|
+
const out = [];
|
|
206907
|
+
for (const [k, t] of rows) {
|
|
206908
|
+
const [team, customer] = k.split("\0");
|
|
206909
|
+
if (app !== void 0 && team !== app) continue;
|
|
206910
|
+
out.push(this.view(team, customer, t, now));
|
|
206911
|
+
}
|
|
206912
|
+
return out.sort((a, b) => b.costBeforeUsd - a.costBeforeUsd);
|
|
206913
|
+
}
|
|
206914
|
+
view(team, customer, t = emptyTotals(), now = /* @__PURE__ */ new Date()) {
|
|
206915
|
+
const today = this.spent(team, customer, "day", now);
|
|
206916
|
+
const month = this.spent(team, customer, "month", now);
|
|
206917
|
+
const round2 = (n) => Math.round(n * 1e6) / 1e6;
|
|
206918
|
+
return {
|
|
206919
|
+
team,
|
|
206920
|
+
customer,
|
|
206921
|
+
...view(t),
|
|
206922
|
+
lastSeen: this.lastSeen(team, customer),
|
|
206923
|
+
spentToday: { usd: round2(today.usd), tokens: today.tokens },
|
|
206924
|
+
spentThisMonth: { usd: round2(month.usd), tokens: month.tokens }
|
|
206925
|
+
};
|
|
206926
|
+
}
|
|
206927
|
+
// ---------------------------------------------------------- shared storage
|
|
206928
|
+
/** Spend recorded since the last flush, by period key and field; marks it flushed. */
|
|
206929
|
+
takeSpendDeltas(now = /* @__PURE__ */ new Date()) {
|
|
206930
|
+
this.prune(now);
|
|
206931
|
+
const out = [];
|
|
206932
|
+
for (const [k, s] of this.local) {
|
|
206933
|
+
const f = this.flushed.get(k) ?? { usd: 0, tokens: 0 };
|
|
206934
|
+
const [period, ...rest] = k.split("\0");
|
|
206935
|
+
const field = rest.join("\0");
|
|
206936
|
+
if (s.usd !== f.usd) out.push({ period, field: `${field}\0usd`, by: s.usd - f.usd });
|
|
206937
|
+
if (s.tokens !== f.tokens) out.push({ period, field: `${field}\0tokens`, by: s.tokens - f.tokens });
|
|
206938
|
+
this.flushed.set(k, { ...s });
|
|
206939
|
+
}
|
|
206940
|
+
return out;
|
|
206941
|
+
}
|
|
206942
|
+
/** Every copy's spend for one period, as read from shared storage (fields: team, customer, usd|tokens). */
|
|
206943
|
+
setSharedSpend(period, fields) {
|
|
206944
|
+
for (const k of [...this.shared.keys()]) if (k.startsWith(`${period}\0`)) this.shared.delete(k);
|
|
206945
|
+
for (const [field, value] of Object.entries(fields)) {
|
|
206946
|
+
const parts = field.split("\0");
|
|
206947
|
+
const name = parts.pop();
|
|
206948
|
+
if (name !== "usd" && name !== "tokens") continue;
|
|
206949
|
+
const k = key(period, ...parts);
|
|
206950
|
+
const s = this.shared.get(k) ?? { usd: 0, tokens: 0 };
|
|
206951
|
+
s[name] = Number(value);
|
|
206952
|
+
this.shared.set(k, s);
|
|
206953
|
+
}
|
|
206954
|
+
}
|
|
206955
|
+
/** Periods to read back from shared storage. */
|
|
206956
|
+
currentPeriods(now = /* @__PURE__ */ new Date()) {
|
|
206957
|
+
return this.prune(now);
|
|
206958
|
+
}
|
|
206959
|
+
};
|
|
206960
|
+
|
|
206961
|
+
// src/gateway/config.ts
|
|
206962
|
+
var DEFAULTS = {
|
|
206963
|
+
port: 8080,
|
|
206964
|
+
host: "0.0.0.0",
|
|
206965
|
+
mode: "optimize",
|
|
206966
|
+
level: "auto",
|
|
206967
|
+
safe: { prompt: true, minifyJson: true, collapseRepeats: true, dedupeToolOutputs: true, dedupePassages: true, dedupeImages: true, promptCaching: true, dedupeRetries: true, cacheEmbeddings: true },
|
|
206968
|
+
optIn: { elideOldToolOutputs: false, lazyTools: false, trimToolFields: false, compactJson: false, parkToolOutputs: false, flex: false, warmCache: false },
|
|
206969
|
+
verify: { sampleRate: 0.05, minSimilarity: 0.8, maxShareOfSavings: 0.2, freeChecks: 5, probation: 5 },
|
|
206970
|
+
runBudget: {},
|
|
206971
|
+
batch: { flushSeconds: 60, pollSeconds: 60, maxRequests: 1e3, discount: 0.5 },
|
|
206972
|
+
flexDiscount: 0.5,
|
|
206973
|
+
embeddingsCacheMB: 256,
|
|
206974
|
+
// Models carry their own long-context price (src/core/models.ts); entries here override or add to them.
|
|
206975
|
+
priceTiers: [],
|
|
206976
|
+
storage: { keyPrefix: "token-lens:", flushSeconds: 10, runDays: 7 },
|
|
206977
|
+
retryWindowSeconds: 120,
|
|
206978
|
+
blockOverContext: false,
|
|
206979
|
+
upstreams: {},
|
|
206980
|
+
routes: [],
|
|
206981
|
+
models: [],
|
|
206982
|
+
clientKeys: [],
|
|
206983
|
+
customers: CUSTOMER_DEFAULTS,
|
|
206984
|
+
isolateTeams: false,
|
|
206985
|
+
maxBodyBytes: 32 * 1024 * 1024,
|
|
206986
|
+
maxRuns: 2e3,
|
|
206987
|
+
logRequests: true,
|
|
206988
|
+
reportToolArgs: false
|
|
206989
|
+
};
|
|
206990
|
+
|
|
206674
206991
|
// src/gateway/optimize.ts
|
|
206675
|
-
import { createHash as
|
|
206992
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
206676
206993
|
|
|
206677
206994
|
// src/gateway/images.ts
|
|
206678
|
-
import { createHash } from "node:crypto";
|
|
206679
|
-
var keyOf = (s) =>
|
|
206995
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
206996
|
+
var keyOf = (s) => createHash2("sha256").update(s).digest("hex").slice(0, 24);
|
|
206680
206997
|
function imageSize(buf) {
|
|
206681
206998
|
if (buf.length < 24) return void 0;
|
|
206682
206999
|
if (buf.readUInt32BE(0) === 2303741511) return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
|
|
@@ -206713,33 +207030,33 @@ function findImages(protocol, body) {
|
|
|
206713
207030
|
parts.forEach((p, idx) => {
|
|
206714
207031
|
if (!p || typeof p !== "object") return;
|
|
206715
207032
|
let data;
|
|
206716
|
-
let
|
|
207033
|
+
let key2;
|
|
206717
207034
|
let detail;
|
|
206718
207035
|
let textPart = (text) => ({ type: "text", text });
|
|
206719
207036
|
if (protocol === "openai-chat" && p.type === "image_url") {
|
|
206720
207037
|
const url = typeof p.image_url === "string" ? p.image_url : p.image_url?.url;
|
|
206721
207038
|
if (typeof url !== "string") return;
|
|
206722
207039
|
data = fromDataUrl(url);
|
|
206723
|
-
|
|
207040
|
+
key2 = keyOf(url);
|
|
206724
207041
|
detail = p.image_url?.detail;
|
|
206725
207042
|
} else if (protocol === "openai-responses" && p.type === "input_image") {
|
|
206726
207043
|
if (typeof p.image_url !== "string") return;
|
|
206727
207044
|
data = fromDataUrl(p.image_url);
|
|
206728
|
-
|
|
207045
|
+
key2 = keyOf(p.image_url);
|
|
206729
207046
|
detail = p.detail;
|
|
206730
207047
|
textPart = (text) => ({ type: "input_text", text });
|
|
206731
207048
|
} else if (protocol === "anthropic" && p.type === "image") {
|
|
206732
207049
|
const src = p.source ?? {};
|
|
206733
207050
|
if (src.type === "base64" && typeof src.data === "string") {
|
|
206734
207051
|
data = Buffer.from(src.data, "base64");
|
|
206735
|
-
|
|
206736
|
-
} else if (typeof src.url === "string")
|
|
207052
|
+
key2 = keyOf(src.data);
|
|
207053
|
+
} else if (typeof src.url === "string") key2 = keyOf(src.url);
|
|
206737
207054
|
else return;
|
|
206738
207055
|
} else if (protocol === "anthropic" && p.type === "tool_result" && Array.isArray(p.content)) {
|
|
206739
207056
|
return visit(p.content, turn);
|
|
206740
207057
|
} else return;
|
|
206741
207058
|
const size = data ? imageSize(data) : void 0;
|
|
206742
|
-
out.push({ turn, key, ...size, detail, replace: (text) => parts[idx] = textPart(text) });
|
|
207059
|
+
out.push({ turn, key: key2, ...size, detail, replace: (text) => parts[idx] = textPart(text) });
|
|
206743
207060
|
});
|
|
206744
207061
|
};
|
|
206745
207062
|
const list = protocol === "openai-responses" ? Array.isArray(body?.input) ? body.input : [] : Array.isArray(body?.messages) ? body.messages : [];
|
|
@@ -206797,18 +207114,18 @@ function canonicalArgs(args) {
|
|
|
206797
207114
|
const walk = (x) => Array.isArray(x) ? x.map(walk) : x && typeof x === "object" ? Object.fromEntries(Object.keys(x).sort().map((k) => [k, walk(x[k])])) : x;
|
|
206798
207115
|
return JSON.stringify(walk(v ?? {}));
|
|
206799
207116
|
}
|
|
206800
|
-
var textRef = (part, turn, obj,
|
|
207117
|
+
var textRef = (part, turn, obj, key2) => ({
|
|
206801
207118
|
part,
|
|
206802
207119
|
turn,
|
|
206803
|
-
get: () => obj[
|
|
207120
|
+
get: () => obj[key2],
|
|
206804
207121
|
set: (t) => {
|
|
206805
|
-
obj[
|
|
207122
|
+
obj[key2] = t;
|
|
206806
207123
|
}
|
|
206807
207124
|
});
|
|
206808
207125
|
var readOnly = (part, turn, text) => ({ part, turn, get: () => text });
|
|
206809
|
-
function contentRefs(part, turn, holder,
|
|
206810
|
-
const content = holder[
|
|
206811
|
-
if (typeof content === "string") return [textRef(part, turn, holder,
|
|
207126
|
+
function contentRefs(part, turn, holder, key2, types) {
|
|
207127
|
+
const content = holder[key2];
|
|
207128
|
+
if (typeof content === "string") return [textRef(part, turn, holder, key2)];
|
|
206812
207129
|
if (!Array.isArray(content)) return [];
|
|
206813
207130
|
return content.filter((b) => b && typeof b === "object" && types.includes(b.type) && typeof b.text === "string").map((b) => textRef(part, turn, b, "text"));
|
|
206814
207131
|
}
|
|
@@ -206965,6 +207282,11 @@ function mergeUsage(a, b) {
|
|
|
206965
207282
|
function usageIn(obj) {
|
|
206966
207283
|
return readUsage(obj?.usage ?? obj?.message?.usage ?? obj?.response?.usage);
|
|
206967
207284
|
}
|
|
207285
|
+
function errorBody(protocol, status, message) {
|
|
207286
|
+
if (protocol === "anthropic")
|
|
207287
|
+
return { type: "error", error: { type: status === 401 ? "authentication_error" : "invalid_request_error", message } };
|
|
207288
|
+
return { error: { message, type: status === 401 ? "authentication_error" : "invalid_request_error", code: null } };
|
|
207289
|
+
}
|
|
206968
207290
|
|
|
206969
207291
|
// src/gateway/optimize.ts
|
|
206970
207292
|
var Counter = class {
|
|
@@ -207103,18 +207425,18 @@ function dedupePassages(text, turn, seenAt) {
|
|
|
207103
207425
|
let changed = false;
|
|
207104
207426
|
const inThisMessage = /* @__PURE__ */ new Set();
|
|
207105
207427
|
for (let i = 0; i < pieces.length; i += 2) {
|
|
207106
|
-
const
|
|
207107
|
-
if (
|
|
207108
|
-
const at = seenAt.get(
|
|
207109
|
-
if (inThisMessage.has(
|
|
207428
|
+
const key2 = pieces[i].trim().replace(/\s+/g, " ");
|
|
207429
|
+
if (key2.length < 200 || key2.startsWith("[Token Lens:")) continue;
|
|
207430
|
+
const at = seenAt.get(key2);
|
|
207431
|
+
if (inThisMessage.has(key2)) {
|
|
207110
207432
|
pieces[i] = "[Token Lens: same passage as earlier in this message]";
|
|
207111
207433
|
changed = true;
|
|
207112
207434
|
} else if (at !== void 0 && at !== turn) {
|
|
207113
207435
|
pieces[i] = `[Token Lens: same passage as in message ${at} above]`;
|
|
207114
207436
|
changed = true;
|
|
207115
207437
|
} else {
|
|
207116
|
-
if (at === void 0) seenAt.set(
|
|
207117
|
-
inThisMessage.add(
|
|
207438
|
+
if (at === void 0) seenAt.set(key2, turn);
|
|
207439
|
+
inThisMessage.add(key2);
|
|
207118
207440
|
}
|
|
207119
207441
|
}
|
|
207120
207442
|
return changed ? pieces.join("") : null;
|
|
@@ -207174,11 +207496,11 @@ function optimizeRequest(input) {
|
|
|
207174
207496
|
if (cfg.safe.dedupeToolOutputs) {
|
|
207175
207497
|
const first = /* @__PURE__ */ new Map();
|
|
207176
207498
|
const firstSeen = (t, turn) => {
|
|
207177
|
-
const
|
|
207178
|
-
if (
|
|
207179
|
-
const at = first.get(
|
|
207499
|
+
const key2 = t.trim();
|
|
207500
|
+
if (key2.length < 200) return null;
|
|
207501
|
+
const at = first.get(key2);
|
|
207180
207502
|
if (at === void 0) {
|
|
207181
|
-
first.set(
|
|
207503
|
+
first.set(key2, turn);
|
|
207182
207504
|
return null;
|
|
207183
207505
|
}
|
|
207184
207506
|
return at === turn ? null : `[Token Lens: same output as the earlier tool result in message ${at}, which is unchanged above]`;
|
|
@@ -207251,7 +207573,7 @@ function applyPromptCaching(input, counts2) {
|
|
|
207251
207573
|
if (parsed.protocol === "anthropic") return anthropicCaching(body) ? { id: "prompt-caching", saved: 0 } : null;
|
|
207252
207574
|
const wantKey = input.upstream.type === "anthropic" || (input.upstream.promptCacheKey ?? /(^|\.)api\.openai\.com$/.test(new URL(input.upstream.baseUrl).hostname));
|
|
207253
207575
|
if (!wantKey || body.prompt_cache_key !== void 0) return null;
|
|
207254
|
-
body.prompt_cache_key = `tl-${
|
|
207576
|
+
body.prompt_cache_key = `tl-${createHash3("sha256").update(input.runId).digest("hex").slice(0, 24)}`;
|
|
207255
207577
|
return { id: "prompt-caching", saved: 0 };
|
|
207256
207578
|
}
|
|
207257
207579
|
|
|
@@ -207343,7 +207665,7 @@ function pricedFor(profile, inputTokens) {
|
|
|
207343
207665
|
if (!lc || !profile.pricing || inputTokens <= lc.aboveTokens) return profile;
|
|
207344
207666
|
return { ...profile, pricing: { ...profile.pricing, inputPerMTok: lc.inputPerMTok, outputPerMTok: lc.outputPerMTok } };
|
|
207345
207667
|
}
|
|
207346
|
-
var hash = (s) =>
|
|
207668
|
+
var hash = (s) => createHash4("sha256").update(s).digest("hex");
|
|
207347
207669
|
var TokenLens = class {
|
|
207348
207670
|
options;
|
|
207349
207671
|
counter = new Counter();
|
|
@@ -207361,25 +207683,57 @@ var TokenLens = class {
|
|
|
207361
207683
|
rewriteMatched = 0;
|
|
207362
207684
|
/** Safe fixes switched off because a check found a different answer while they were in play. */
|
|
207363
207685
|
disabled = /* @__PURE__ */ new Set();
|
|
207686
|
+
customers;
|
|
207687
|
+
ledger;
|
|
207364
207688
|
constructor(options = {}) {
|
|
207365
207689
|
this.options = options;
|
|
207366
207690
|
this.runId = options.run ?? randomUUID();
|
|
207691
|
+
this.customers = { ...CUSTOMER_DEFAULTS, ...options.customers, teamBudgets: {} };
|
|
207692
|
+
this.ledger = new CustomerLedger(this.customers.maxCustomers);
|
|
207367
207693
|
}
|
|
207368
207694
|
// ------------------------------------------------------------ hooks
|
|
207369
207695
|
/** A fetch that applies Token Lens to OpenAI and Anthropic API calls and passes everything else through. */
|
|
207370
207696
|
fetch(base) {
|
|
207371
207697
|
const real = base ?? ((input, init) => globalThis.fetch(input, init));
|
|
207372
|
-
return async (input,
|
|
207698
|
+
return async (input, original) => {
|
|
207373
207699
|
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
207374
207700
|
const path = new URL(url).pathname;
|
|
207701
|
+
const ours = /* @__PURE__ */ new Map();
|
|
207702
|
+
let init = original;
|
|
207703
|
+
if (original?.headers) {
|
|
207704
|
+
const headers = new Headers(original.headers);
|
|
207705
|
+
for (const [k, v] of headers) if (k.startsWith("x-token-lens-")) ours.set(k, v);
|
|
207706
|
+
if (ours.size) {
|
|
207707
|
+
for (const k of ours.keys()) headers.delete(k);
|
|
207708
|
+
init = { ...original, headers };
|
|
207709
|
+
}
|
|
207710
|
+
}
|
|
207375
207711
|
const protocol = PATHS.find(([re]) => re.test(path))?.[1];
|
|
207376
207712
|
if (!protocol || (init?.method ?? "GET").toUpperCase() !== "POST" || typeof init?.body !== "string") return real(input, init);
|
|
207377
207713
|
let prep;
|
|
207378
207714
|
try {
|
|
207379
|
-
prep = this.prepare(protocol, init.body, new URL(url).origin);
|
|
207715
|
+
prep = this.prepare(protocol, init.body, new URL(url).origin, ours.get("x-token-lens-customer"));
|
|
207380
207716
|
} catch {
|
|
207381
207717
|
return real(input, init);
|
|
207382
207718
|
}
|
|
207719
|
+
if (prep.customer) {
|
|
207720
|
+
let budget;
|
|
207721
|
+
try {
|
|
207722
|
+
budget = customerBudget(this.customers, ours.get("x-token-lens-plan"), (n) => ours.get(n));
|
|
207723
|
+
} catch (e) {
|
|
207724
|
+
if (e instanceof UnknownPlanError) return Response.json(errorBody(protocol, 400, e.message), { status: 400 });
|
|
207725
|
+
throw e;
|
|
207726
|
+
}
|
|
207727
|
+
if (hasLimits(budget)) {
|
|
207728
|
+
const over = this.ledger.over("app", prep.customer, { customer: budget, team: {} });
|
|
207729
|
+
if (over) {
|
|
207730
|
+
const period = over.limit.startsWith("daily") ? "daily" : "monthly";
|
|
207731
|
+
const message = `Token Lens stopped this call: customer "${prep.customer}" reached its ${period} limit (${over.limit.endsWith("Usd") ? `$${over.spent.toFixed(4)} of $${over.cap}` : `${over.spent} of ${over.cap} tokens`}).`;
|
|
207732
|
+
this.record({ ...this.base(prep), status: 429, usage: {}, replayed: false });
|
|
207733
|
+
return Response.json(errorBody(protocol, 429, message), { status: 429, headers: { "x-token-lens-budget": `customer-${period}-${over.limit.endsWith("Usd") ? "usd" : "tokens"}` } });
|
|
207734
|
+
}
|
|
207735
|
+
}
|
|
207736
|
+
}
|
|
207383
207737
|
return this.send(real, input, init, prep);
|
|
207384
207738
|
};
|
|
207385
207739
|
}
|
|
@@ -207427,9 +207781,18 @@ var TokenLens = class {
|
|
|
207427
207781
|
byFix: [...this.byFix].map(([fix, f]) => ({ fix, calls: f.calls, tokens: f.tokens, usd: Math.round(f.usd * 1e6) / 1e6 })),
|
|
207428
207782
|
checks: { ...summarizeChecks(this.checks), recent: this.checks.slice(-20) },
|
|
207429
207783
|
rewritePrompts: !this.options.rewritePrompts ? "off" : this.rewriteStopped ? "switched off: an output check found a different answer" : this.onProbation() ? `on probation: ${this.rewriteMatched} of ${this.probation()} answers matched so far` : "on",
|
|
207430
|
-
switchedOff: [...this.disabled]
|
|
207784
|
+
switchedOff: [...this.disabled],
|
|
207785
|
+
byCustomer: this.ledger.list("app").map(({ team: _t, ...r }) => r)
|
|
207431
207786
|
};
|
|
207432
207787
|
}
|
|
207788
|
+
/** One customer's savings, and spend today and this month (limits count these), or undefined before its first call. */
|
|
207789
|
+
customer(id) {
|
|
207790
|
+
const stored = storedId(id, this.customers);
|
|
207791
|
+
const row = this.ledger.snapshot().get(`app\0${stored}`);
|
|
207792
|
+
if (!row) return void 0;
|
|
207793
|
+
const { team: _t, ...r } = this.ledger.view("app", stored, row);
|
|
207794
|
+
return r;
|
|
207795
|
+
}
|
|
207433
207796
|
/** The report in a few plain lines. */
|
|
207434
207797
|
summary() {
|
|
207435
207798
|
const r = this.report();
|
|
@@ -207467,8 +207830,9 @@ var TokenLens = class {
|
|
|
207467
207830
|
optIn: Object.fromEntries(Object.keys(DEFAULTS.optIn).map((k) => [k, false]))
|
|
207468
207831
|
};
|
|
207469
207832
|
}
|
|
207470
|
-
prepare(protocol, raw, origin) {
|
|
207833
|
+
prepare(protocol, raw, origin, customerHeader) {
|
|
207471
207834
|
const body = JSON.parse(raw);
|
|
207835
|
+
const customer = customerOf(customerHeader, body, this.customers);
|
|
207472
207836
|
const parsed = parseRequest(protocol, body);
|
|
207473
207837
|
const profile = this.profile(parsed.model);
|
|
207474
207838
|
const before = countParts(parsed.refs, profile, this.counter);
|
|
@@ -207507,15 +207871,16 @@ var TokenLens = class {
|
|
|
207507
207871
|
after,
|
|
207508
207872
|
// As in the gateway: the sum of what each fix removed (estimates count the line breaks it drops).
|
|
207509
207873
|
savedInput: actions.reduce((n, a) => n + a.saved, 0),
|
|
207510
|
-
body: actions.length ? JSON.stringify(body) : raw
|
|
207874
|
+
body: actions.length ? JSON.stringify(body) : raw,
|
|
207875
|
+
customer
|
|
207511
207876
|
};
|
|
207512
207877
|
}
|
|
207513
207878
|
async send(real, input, init, prep) {
|
|
207514
207879
|
const { parsed } = prep;
|
|
207515
207880
|
const replayable = (this.options.fixes?.dedupeRetries ?? true) && this.options.mode !== "observe" && !parsed.stream && parsed.temperature === 0;
|
|
207516
|
-
const
|
|
207881
|
+
const key2 = replayable ? hash(`${prep.customer ?? ""}\0${prep.protocol}\0${String(input instanceof Request ? input.url : input)}\0${prep.body}`) : "";
|
|
207517
207882
|
const windowMs = (this.options.retryWindowSeconds ?? 120) * 1e3;
|
|
207518
|
-
const cached = replayable ? this.replays.get(
|
|
207883
|
+
const cached = replayable ? this.replays.get(key2) : void 0;
|
|
207519
207884
|
if (cached && Date.now() - cached.at < windowMs) {
|
|
207520
207885
|
this.record({ ...this.base(prep), status: cached.status, usage: cached.usage, replayed: true });
|
|
207521
207886
|
return new Response(cached.text, { status: cached.status, headers: [...cached.headers, ["x-token-lens-replayed", "true"]] });
|
|
@@ -207569,7 +207934,7 @@ var TokenLens = class {
|
|
|
207569
207934
|
const usage = usageIn(json);
|
|
207570
207935
|
this.record({ ...this.base(prep), status: res.status, usage, replayed: false });
|
|
207571
207936
|
if (replayable && res.ok && text) {
|
|
207572
|
-
this.replays.set(
|
|
207937
|
+
this.replays.set(key2, { at: Date.now(), status: res.status, headers: [...res.headers], text, usage });
|
|
207573
207938
|
if (this.replays.size > 500) this.replays.delete(this.replays.keys().next().value);
|
|
207574
207939
|
}
|
|
207575
207940
|
if (res.ok && json && prep.savedInput > 0) this.maybeCheck(real, input, init, prep, json);
|
|
@@ -207647,7 +208012,8 @@ var TokenLens = class {
|
|
|
207647
208012
|
stream: prep.parsed.stream,
|
|
207648
208013
|
savedInput: prep.savedInput,
|
|
207649
208014
|
actions: prep.actions,
|
|
207650
|
-
estimatedInput: prep.after.total
|
|
208015
|
+
estimatedInput: prep.after.total,
|
|
208016
|
+
customer: prep.customer
|
|
207651
208017
|
};
|
|
207652
208018
|
}
|
|
207653
208019
|
record(c) {
|
|
@@ -207659,6 +208025,7 @@ var TokenLens = class {
|
|
|
207659
208025
|
if (!row) this.rows.set(profile.id, row = emptyTotals());
|
|
207660
208026
|
const savedInput = c.status < 400 ? c.savedInput : 0;
|
|
207661
208027
|
addCall(row, { profile, status: c.status, usage, estimatedInput: c.estimatedInput, savedInput, replayed: c.replayed });
|
|
208028
|
+
this.ledger.record("app", c.customer, { profile, status: c.status, usage, estimatedInput: c.estimatedInput, savedInput, replayed: c.replayed });
|
|
207662
208029
|
const unoptimized = pricedFor(c.profile, input + savedInput);
|
|
207663
208030
|
if (!c.replayed && unoptimized !== profile) {
|
|
207664
208031
|
const u = { ...usage, inputTokens: input };
|
|
@@ -207685,6 +208052,7 @@ var TokenLens = class {
|
|
|
207685
208052
|
}
|
|
207686
208053
|
this.options.onCall?.({
|
|
207687
208054
|
protocol: c.protocol,
|
|
208055
|
+
...c.customer ? { customer: c.customer } : {},
|
|
207688
208056
|
model: c.model,
|
|
207689
208057
|
profile: profile.id,
|
|
207690
208058
|
status: c.status,
|