@konneal/engine 0.2.72 → 0.2.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ask-UOTYZ5DC.js → ask-MFFOHVQ2.js} +2 -2
- package/dist/{chunk-TPV5GPY2.js → chunk-LL3LWBZH.js} +73 -42
- package/dist/{chunk-FLNX4O7C.js → chunk-OXTRNMTZ.js} +1 -1
- package/dist/{chunk-UW5M2AXR.js → chunk-V7PEVNNO.js} +44 -8
- package/dist/quota.d.ts +8 -0
- package/dist/{search-PBOODCMQ.js → search-LN5Q7OSZ.js} +2 -2
- package/dist/worker_public/src/index.js +4 -4
- package/package.json +1 -1
- package/workers/worker_public/src/ask.ts +46 -7
- package/workers/worker_public/src/quota.ts +42 -1
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
THRESHOLDS,
|
|
10
10
|
answerEffort,
|
|
11
11
|
effortBudget,
|
|
12
|
+
num,
|
|
12
13
|
processExpansion,
|
|
13
14
|
sha256Hex,
|
|
14
15
|
today
|
|
@@ -817,8 +818,8 @@ var BaseUrnGenerator = class {
|
|
|
817
818
|
const ed = this.maybe("edition");
|
|
818
819
|
if (ed === void 0 || ed === null)
|
|
819
820
|
return void 0;
|
|
820
|
-
const
|
|
821
|
-
return
|
|
821
|
+
const num2 = typeof ed === "object" && "number" in ed ? ed.number : ed;
|
|
822
|
+
return num2 === void 0 || num2 === null || num2 === "" ? void 0 : `ed.${String(num2)}`;
|
|
822
823
|
}
|
|
823
824
|
urnLanguage() {
|
|
824
825
|
const langs = this.maybe("languages");
|
|
@@ -7139,9 +7140,9 @@ var IsoIdentifier = class _IsoIdentifier extends BaseIdentifier {
|
|
|
7139
7140
|
}
|
|
7140
7141
|
}
|
|
7141
7142
|
}
|
|
7142
|
-
const
|
|
7143
|
-
if (
|
|
7144
|
-
result += result === "" ?
|
|
7143
|
+
const num2 = this.numberPortion(true);
|
|
7144
|
+
if (num2 !== "")
|
|
7145
|
+
result += result === "" ? num2 : ` ${num2}`;
|
|
7145
7146
|
result += this.languagePortion(this.languageSingle());
|
|
7146
7147
|
if (this.all_parts === true)
|
|
7147
7148
|
result += " (all parts)";
|
|
@@ -7306,9 +7307,9 @@ var IsoSingleIdentifier = class extends IsoIdentifier {
|
|
|
7306
7307
|
result += `${sep}${stage}`;
|
|
7307
7308
|
}
|
|
7308
7309
|
}
|
|
7309
|
-
const
|
|
7310
|
-
if (
|
|
7311
|
-
result += result === "" ?
|
|
7310
|
+
const num2 = this.numberPortion(true);
|
|
7311
|
+
if (num2 !== "")
|
|
7312
|
+
result += result === "" ? num2 : ` ${num2}`;
|
|
7312
7313
|
result += this.languagePortion(this.languageSingle());
|
|
7313
7314
|
if (this.all_parts === true)
|
|
7314
7315
|
result += " (all parts)";
|
|
@@ -7611,11 +7612,11 @@ var IsoSupplementIdentifier = class extends IsoSingleIdentifier {
|
|
|
7611
7612
|
const ts = this.typedStageState;
|
|
7612
7613
|
const stage = ts === void 0 ? "" : stageAbbr(ts.entry, long, ts.originalAbbr);
|
|
7613
7614
|
let result = `${baseStr}/${stage}`;
|
|
7614
|
-
const
|
|
7615
|
-
if (
|
|
7616
|
-
if (!
|
|
7615
|
+
const num2 = this.numberPortion(true);
|
|
7616
|
+
if (num2 !== "") {
|
|
7617
|
+
if (!num2.startsWith(":") && !stage.endsWith("."))
|
|
7617
7618
|
result += " ";
|
|
7618
|
-
result +=
|
|
7619
|
+
result += num2;
|
|
7619
7620
|
}
|
|
7620
7621
|
result += this.languagePortion(this.languageSingle());
|
|
7621
7622
|
return result;
|
|
@@ -7670,20 +7671,20 @@ var IsoDirectives = class extends IsoSingleIdentifier {
|
|
|
7670
7671
|
const abbr = this.typedStageState === void 0 ? "" : stageAbbr(this.typedStageState.entry, false);
|
|
7671
7672
|
if (abbr !== "")
|
|
7672
7673
|
head += ` ${abbr}`;
|
|
7673
|
-
let
|
|
7674
|
+
let num2 = this.number ?? "";
|
|
7674
7675
|
if (this.part !== void 0)
|
|
7675
|
-
|
|
7676
|
+
num2 += ` ${this.part}`;
|
|
7676
7677
|
if (this.subpart !== void 0)
|
|
7677
|
-
|
|
7678
|
+
num2 += `-${this.subpart}`;
|
|
7678
7679
|
if (this.stage_iteration !== void 0)
|
|
7679
|
-
|
|
7680
|
+
num2 += `.${this.stage_iteration.render() ?? ""}`;
|
|
7680
7681
|
if (this.date !== void 0 && this.date.present())
|
|
7681
|
-
|
|
7682
|
-
if (
|
|
7682
|
+
num2 += `:${this.date.render()}`;
|
|
7683
|
+
if (num2 === "")
|
|
7683
7684
|
return head;
|
|
7684
|
-
if (
|
|
7685
|
-
return head +
|
|
7686
|
-
return `${head} ${
|
|
7685
|
+
if (num2.startsWith(":"))
|
|
7686
|
+
return head + num2;
|
|
7687
|
+
return `${head} ${num2}`;
|
|
7687
7688
|
}
|
|
7688
7689
|
};
|
|
7689
7690
|
registerType(IsoDirectives);
|
|
@@ -8606,9 +8607,9 @@ function preprocessNist(input) {
|
|
|
8606
8607
|
cleaned = cleaned.replace(/(?<!e)(\d)(rev\d{4})/g, "$1 $2");
|
|
8607
8608
|
cleaned = cleaned.replace(/(\d+e\d+)\.([A-Za-z]{3,9}\d{4})/g, "$1rev$2");
|
|
8608
8609
|
if (/\bIR\b/.test(cleaned) && !cleaned.includes("CIRC")) {
|
|
8609
|
-
cleaned = cleaned.replace(/(\d)r(\d{1,2})\/(\d{2,4})/g, (_m,
|
|
8610
|
+
cleaned = cleaned.replace(/(\d)r(\d{1,2})\/(\d{2,4})/g, (_m, num2, mon, yr) => {
|
|
8610
8611
|
const yyyy = yr.length === 2 ? `19${yr}` : yr;
|
|
8611
|
-
return `${
|
|
8612
|
+
return `${num2}/Upd1-${yyyy}${mon.padStart(2, "0")}`;
|
|
8612
8613
|
});
|
|
8613
8614
|
}
|
|
8614
8615
|
if (!cleaned.includes("LCIRC") && !cleaned.includes("CIRC")) {
|
|
@@ -11321,17 +11322,17 @@ function normalizeJointStageSpellings(input) {
|
|
|
11321
11322
|
let cleaned = input;
|
|
11322
11323
|
const pubs = "((?:ISO/IEC/IEEE|IEEE/ISO/IEC|IEEE/IEC/ISO|ISO/IEEE|IEC/IEEE|IEEE/IEC|ISO/IEC)(?:/ ?| ))";
|
|
11323
11324
|
const stage = "(FDIS|FCD|CDV|DIS\\d?|CD\\d?|WD|PWI|NP)";
|
|
11324
|
-
const
|
|
11325
|
+
const num2 = "(P?\\d+(?:[.-]\\d+)?)";
|
|
11325
11326
|
const tail = "(,? (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Sept|Oct|Nov|Dec)[a-z]* \\d{4}|$|-\\d{4}(?:-\\d\\d)?)";
|
|
11326
11327
|
cleaned = cleaned.replace(/(\d)_D(\d)/g, "$1/D$2");
|
|
11327
11328
|
cleaned = cleaned.replace(/,(?=[A-Za-z])/, ", ");
|
|
11328
|
-
cleaned = cleaned.replace(new RegExp(`^${pubs}${
|
|
11329
|
-
cleaned = cleaned.replace(new RegExp(`^${pubs}${
|
|
11330
|
-
cleaned = cleaned.replace(new RegExp(`^${pubs}${
|
|
11331
|
-
cleaned = cleaned.replace(new RegExp(`^${pubs}${
|
|
11332
|
-
cleaned = cleaned.replace(new RegExp(`^${pubs}${
|
|
11333
|
-
cleaned = cleaned.replace(new RegExp(`^${pubs}${
|
|
11334
|
-
cleaned = cleaned.replace(new RegExp(`^${pubs}${stage} ${
|
|
11329
|
+
cleaned = cleaned.replace(new RegExp(`^${pubs}${num2}/(D[\\d.]+)/ ?${stage}\\b`), "$1$4 $2/$3");
|
|
11330
|
+
cleaned = cleaned.replace(new RegExp(`^${pubs}${num2}/ ${stage}${tail}`), "$1$3 $2$4");
|
|
11331
|
+
cleaned = cleaned.replace(new RegExp(`^${pubs}${num2}/${stage} (\\d{4})\\b`), "$1$3 $2-$4");
|
|
11332
|
+
cleaned = cleaned.replace(new RegExp(`^${pubs}${num2} ${stage}${tail}`), "$1$3 $2$4");
|
|
11333
|
+
cleaned = cleaned.replace(new RegExp(`^${pubs}${num2} ${stage} (D\\d+)\\b`), "$1$3 $2/$4");
|
|
11334
|
+
cleaned = cleaned.replace(new RegExp(`^${pubs}${num2}/ ?${stage}(-(?:19|20)\\d\\d(?:-\\d\\d)?)`), "$1$3 $2$4");
|
|
11335
|
+
cleaned = cleaned.replace(new RegExp(`^${pubs}${stage} ${num2}/? ?(D\\d+)\\b`), "$1$2 $3/$4");
|
|
11335
11336
|
return cleaned;
|
|
11336
11337
|
}
|
|
11337
11338
|
function preprocessIeee(input) {
|
|
@@ -14556,8 +14557,8 @@ var CenBuilder = class {
|
|
|
14556
14557
|
const numStr = (str3(d["number"]) ?? "").trim();
|
|
14557
14558
|
if (!/^\d{1,6}$/.test(numStr))
|
|
14558
14559
|
return void 0;
|
|
14559
|
-
const
|
|
14560
|
-
if (!(
|
|
14560
|
+
const num2 = Number.parseInt(numStr, 10);
|
|
14561
|
+
if (!(num2 >= 6e4 && num2 <= 79999))
|
|
14561
14562
|
return void 0;
|
|
14562
14563
|
const impl = grammarImplementation("iec");
|
|
14563
14564
|
if (impl === void 0)
|
|
@@ -19006,9 +19007,9 @@ var CsaAdoptedClass = (() => {
|
|
|
19006
19007
|
baseStr = baseStr.replace(`:${year}`, `:${year.slice(2)}`);
|
|
19007
19008
|
}
|
|
19008
19009
|
}
|
|
19009
|
-
baseStr = baseStr.replace(/\/Amd\s+(\d+)([:/-])(\d{2,4})\b/g, (_m,
|
|
19010
|
+
baseStr = baseStr.replace(/\/Amd\s+(\d+)([:/-])(\d{2,4})\b/g, (_m, num2, sep, year) => {
|
|
19010
19011
|
const short = year.length === 4 && (year.startsWith("20") || year.startsWith("19")) ? year.slice(2) : year;
|
|
19011
|
-
return `/A${
|
|
19012
|
+
return `/A${num2}${sep}${short}`;
|
|
19012
19013
|
});
|
|
19013
19014
|
let result;
|
|
19014
19015
|
if (this.publisher_prefix !== void 0 && this.publisher_prefix.endsWith("-")) {
|
|
@@ -19635,9 +19636,9 @@ function parseCsa(input) {
|
|
|
19635
19636
|
const full = Number(two) < 50 ? `20${two}` : `19${two}`;
|
|
19636
19637
|
wrapped = wrapped.replace(new RegExp(`:${two}\\b`), `:${full}`);
|
|
19637
19638
|
}
|
|
19638
|
-
wrapped = wrapped.replace(/\/A(\d+)([:/-])(\d{2,4})\b/g, (_m,
|
|
19639
|
+
wrapped = wrapped.replace(/\/A(\d+)([:/-])(\d{2,4})\b/g, (_m, num2, sep, year) => {
|
|
19639
19640
|
const full = year.length === 2 ? Number(year) < 50 ? `20${year}` : `19${year}` : year;
|
|
19640
|
-
return `/Amd ${
|
|
19641
|
+
return `/Amd ${num2}${sep}${full}`;
|
|
19641
19642
|
});
|
|
19642
19643
|
const base = parseExternalStandard(wrapped);
|
|
19643
19644
|
if (base === void 0) {
|
|
@@ -21908,13 +21909,13 @@ function parseOimlSpine(display) {
|
|
|
21908
21909
|
const kind = String(h._type ?? "").split(":").pop() ?? "";
|
|
21909
21910
|
const letter = TYPE_LETTER[kind] ?? "";
|
|
21910
21911
|
if (!letter) return null;
|
|
21911
|
-
const
|
|
21912
|
+
const num2 = String(Number(h.number));
|
|
21912
21913
|
const part = h.part !== void 0 ? String(h.part) : void 0;
|
|
21913
21914
|
const ed = h.year !== void 0 ? String(h.year) : h.edition !== void 0 ? String(h.edition) : void 0;
|
|
21914
21915
|
return {
|
|
21915
|
-
doc_number:
|
|
21916
|
+
doc_number: num2,
|
|
21916
21917
|
...ed ? { edition: ed } : {},
|
|
21917
|
-
label: `OIML ${letter} ${
|
|
21918
|
+
label: `OIML ${letter} ${num2}${part ? `-${part}` : ""}${ed ? `:${ed}` : ""}`
|
|
21918
21919
|
};
|
|
21919
21920
|
} catch {
|
|
21920
21921
|
return null;
|
|
@@ -21949,9 +21950,9 @@ var oimlPubid = {
|
|
|
21949
21950
|
for (const m of query.matchAll(re)) {
|
|
21950
21951
|
const [, oimlPrefix, letter, gap, digits, edition] = m;
|
|
21951
21952
|
if (digits.length === 1 && !oimlPrefix && !gap) continue;
|
|
21952
|
-
const
|
|
21953
|
+
const num2 = String(Number(digits));
|
|
21953
21954
|
const type = letter.toUpperCase();
|
|
21954
|
-
return { doc_number:
|
|
21955
|
+
return { doc_number: num2, ...edition ? { edition } : {}, label: `OIML ${type} ${num2}${edition ? `:${edition}` : ""}` };
|
|
21955
21956
|
}
|
|
21956
21957
|
return null;
|
|
21957
21958
|
},
|
|
@@ -24578,6 +24579,32 @@ function telemetry(env, ctx, tier, route, model, ok, answerChars, queryHash, lan
|
|
|
24578
24579
|
])
|
|
24579
24580
|
);
|
|
24580
24581
|
}
|
|
24582
|
+
function estimateTokens(chars) {
|
|
24583
|
+
return Math.ceil(chars / 4);
|
|
24584
|
+
}
|
|
24585
|
+
function tokenTier(bucketId) {
|
|
24586
|
+
return bucketId.startsWith("key:") ? "key" : bucketId.startsWith("sub:") ? "member" : "anon";
|
|
24587
|
+
}
|
|
24588
|
+
function tokenLimit(env, bucketId) {
|
|
24589
|
+
const tier = tokenTier(bucketId);
|
|
24590
|
+
return tier === "key" ? num(env, "KEY_DAY_TOKENS", 4e6) : tier === "member" ? num(env, "MEMBER_DAY_TOKENS", 15e5) : num(env, "ANON_DAY_TOKENS", 3e5);
|
|
24591
|
+
}
|
|
24592
|
+
async function tokenBudget(env, bucketId) {
|
|
24593
|
+
const used = Number(await env.CACHE.get(`t:${today()}:ask:${await sha256Hex(bucketId)}`) ?? "0");
|
|
24594
|
+
return { used, limit: tokenLimit(env, bucketId) };
|
|
24595
|
+
}
|
|
24596
|
+
async function chargeTokens(env, bucketId, tokens) {
|
|
24597
|
+
if (!(tokens > 0)) return;
|
|
24598
|
+
await kvIncr(env.CACHE, `t:${today()}:ask:${await sha256Hex(bucketId)}`, tokens);
|
|
24599
|
+
}
|
|
24600
|
+
function usageTotal(usage) {
|
|
24601
|
+
if (!usage || typeof usage !== "object") return null;
|
|
24602
|
+
const u = usage;
|
|
24603
|
+
const p = Number(u.prompt_tokens ?? 0);
|
|
24604
|
+
const c = Number(u.completion_tokens ?? 0);
|
|
24605
|
+
const total = p + c;
|
|
24606
|
+
return Number.isFinite(total) && total > 0 ? total : null;
|
|
24607
|
+
}
|
|
24581
24608
|
|
|
24582
24609
|
// workers/worker_public/src/graph.ts
|
|
24583
24610
|
function docNumberOf(nodeId) {
|
|
@@ -24662,6 +24689,10 @@ export {
|
|
|
24662
24689
|
clientIp,
|
|
24663
24690
|
checkQuota,
|
|
24664
24691
|
telemetry,
|
|
24692
|
+
estimateTokens,
|
|
24693
|
+
tokenBudget,
|
|
24694
|
+
chargeTokens,
|
|
24695
|
+
usageTotal,
|
|
24665
24696
|
graphExpand,
|
|
24666
24697
|
editionNote
|
|
24667
24698
|
};
|
|
@@ -3,12 +3,14 @@ import {
|
|
|
3
3
|
answerQuality,
|
|
4
4
|
appliedContext,
|
|
5
5
|
buildMessages,
|
|
6
|
+
chargeTokens,
|
|
6
7
|
checkQuota,
|
|
7
8
|
citations,
|
|
8
9
|
clientIp,
|
|
9
10
|
contextNote,
|
|
10
11
|
editionNote,
|
|
11
12
|
embed,
|
|
13
|
+
estimateTokens,
|
|
12
14
|
experimentalSourceLabels,
|
|
13
15
|
generateOnce,
|
|
14
16
|
graphExpand,
|
|
@@ -34,8 +36,10 @@ import {
|
|
|
34
36
|
splitHistory,
|
|
35
37
|
syntheticUnderstanding,
|
|
36
38
|
telemetry,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
tokenBudget,
|
|
40
|
+
understandQuery,
|
|
41
|
+
usageTotal
|
|
42
|
+
} from "./chunk-LL3LWBZH.js";
|
|
39
43
|
import {
|
|
40
44
|
corsHeaders,
|
|
41
45
|
err,
|
|
@@ -2166,7 +2170,23 @@ async function summarizeHistory(env, model, turns) {
|
|
|
2166
2170
|
return null;
|
|
2167
2171
|
}
|
|
2168
2172
|
}
|
|
2169
|
-
|
|
2173
|
+
function messageChars(messages) {
|
|
2174
|
+
let n = 0;
|
|
2175
|
+
for (const m of messages) {
|
|
2176
|
+
if (typeof m.content === "string") n += m.content.length;
|
|
2177
|
+
else if (Array.isArray(m.content)) {
|
|
2178
|
+
for (const part of m.content) if (part?.type === "text" && typeof part.text === "string") n += part.text.length;
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
return n;
|
|
2182
|
+
}
|
|
2183
|
+
async function chargeAnswerTokens(env, bucketId, stats, promptChars, answerChars) {
|
|
2184
|
+
const total = usageTotal(stats?.usage) ?? estimateTokens(promptChars + answerChars);
|
|
2185
|
+
await chargeTokens(env, bucketId, total);
|
|
2186
|
+
const budget = await tokenBudget(env, bucketId);
|
|
2187
|
+
return { tokens_used: budget.used, token_limit: budget.limit };
|
|
2188
|
+
}
|
|
2189
|
+
async function* sseTokens(stream, stats) {
|
|
2170
2190
|
const reader = stream.getReader();
|
|
2171
2191
|
const decoder = new TextDecoder();
|
|
2172
2192
|
let buf = "";
|
|
@@ -2184,6 +2204,7 @@ async function* sseTokens(stream) {
|
|
|
2184
2204
|
try {
|
|
2185
2205
|
const evt = JSON.parse(payload);
|
|
2186
2206
|
const tok = typeof evt?.response === "string" ? evt.response : evt?.choices?.[0]?.delta?.content;
|
|
2207
|
+
if (evt.usage && stats && stats.usage === void 0) stats.usage = evt.usage;
|
|
2187
2208
|
if (tok) yield tok;
|
|
2188
2209
|
} catch {
|
|
2189
2210
|
}
|
|
@@ -2227,6 +2248,12 @@ async function handleAsk(env, ctx, req, tier, key) {
|
|
|
2227
2248
|
if (!quota.ok) {
|
|
2228
2249
|
return err(429, "quota_exceeded", `Daily question limit reached (${quota.limit}). Try again tomorrow.`);
|
|
2229
2250
|
}
|
|
2251
|
+
const tokBudget = await tokenBudget(env, bucketId);
|
|
2252
|
+
if (tokBudget.used >= tokBudget.limit) {
|
|
2253
|
+
return err(429, "quota_exceeded", `Daily token budget reached (${tokBudget.limit} tokens). Try again tomorrow.`);
|
|
2254
|
+
}
|
|
2255
|
+
quota.tokens_used = tokBudget.used;
|
|
2256
|
+
quota.token_limit = tokBudget.limit;
|
|
2230
2257
|
const hardCap = num(env, "ANON_DAY_HARD_CAP", 5e3);
|
|
2231
2258
|
if (tier === "anon" && quota.used > hardCap) {
|
|
2232
2259
|
return err(503, "generation_disabled", "Generation is temporarily paused; search remains available.");
|
|
@@ -2444,14 +2471,17 @@ ${summary}` }] : [],
|
|
|
2444
2471
|
`));
|
|
2445
2472
|
if (readAs()) send({ type: "read", read: readAs() });
|
|
2446
2473
|
send({ type: "citations", citations: [], context_applied: NO_CONTEXT, quota });
|
|
2474
|
+
const stats = {};
|
|
2447
2475
|
let full = "";
|
|
2448
2476
|
try {
|
|
2449
|
-
for await (const tok of sseTokens(stream)) {
|
|
2477
|
+
for await (const tok of sseTokens(stream, stats)) {
|
|
2450
2478
|
full += tok;
|
|
2451
2479
|
send({ type: "token", v: tok });
|
|
2452
2480
|
}
|
|
2453
2481
|
} catch {
|
|
2454
2482
|
}
|
|
2483
|
+
const tq2 = await chargeAnswerTokens(env, bucketId, stats, messageChars(messages2), full.length);
|
|
2484
|
+
send({ type: "quota", quota: { ...quota, ...tq2 } });
|
|
2455
2485
|
send({ type: "done", model, query_hash: queryHash2, context_applied: NO_CONTEXT, read: readAs() });
|
|
2456
2486
|
telemetry(env, ctx, tier, "ask", model, true, full.length, queryHash2, q.lang, void 0, telemetryMeta());
|
|
2457
2487
|
controller.close();
|
|
@@ -2507,7 +2537,8 @@ ${summary}` }] : [],
|
|
|
2507
2537
|
corsHeaders(req)
|
|
2508
2538
|
);
|
|
2509
2539
|
}
|
|
2510
|
-
|
|
2540
|
+
const tq2 = await chargeAnswerTokens(env, bucketId, void 0, q.query.length, verdict.answer.length);
|
|
2541
|
+
return json({ answer: verdict.answer, citations: citations2, model, query_hash: queryHash2, follow_ups: [], context_applied: draftCtxApplied, read: readAs(), ...draftPayload ? { draft: draftPayload } : {}, quota: { ...quota, ...tq2 } });
|
|
2511
2542
|
}
|
|
2512
2543
|
if (apiCallIntent) {
|
|
2513
2544
|
const callCtxApplied = declaredCtx ? appliedContext(declaredCtx, null) : NO_CONTEXT;
|
|
@@ -2534,7 +2565,8 @@ ${summary}` }] : [],
|
|
|
2534
2565
|
corsHeaders(req)
|
|
2535
2566
|
);
|
|
2536
2567
|
}
|
|
2537
|
-
|
|
2568
|
+
const tq2 = await chargeAnswerTokens(env, bucketId, void 0, q.query.length, verdict.answer.length);
|
|
2569
|
+
return json({ answer: verdict.answer, citations: [], model, query_hash: queryHash2, follow_ups: [], context_applied: callCtxApplied, read: readAs(), ...draftPayload ? { draft: draftPayload } : {}, quota: { ...quota, ...tq2 } });
|
|
2538
2570
|
}
|
|
2539
2571
|
let liveRecords;
|
|
2540
2572
|
let accountNote;
|
|
@@ -2834,14 +2866,17 @@ ${q.query}` }],
|
|
|
2834
2866
|
send({ type: "read", read: readAs() });
|
|
2835
2867
|
const sourceQuality = answerQuality(cites.map((c) => c.quality));
|
|
2836
2868
|
send({ type: "citations", citations: cites, context_applied: ctxApplied, ...sourceQuality ? { source_quality: sourceQuality, confidence_note: qualityNote(sourceQuality), ...sourceQuality === "ocr" ? { experimental_sources: experimentalSourceLabels(cites) } : {} } : {}, ...liveRecords ? { records: liveRecords } : {}, quota });
|
|
2869
|
+
const stats = {};
|
|
2837
2870
|
let full = "";
|
|
2838
2871
|
try {
|
|
2839
|
-
for await (const tok of sseTokens(stream)) {
|
|
2872
|
+
for await (const tok of sseTokens(stream, stats)) {
|
|
2840
2873
|
full += tok;
|
|
2841
2874
|
send({ type: "token", v: tok });
|
|
2842
2875
|
}
|
|
2843
2876
|
} catch {
|
|
2844
2877
|
}
|
|
2878
|
+
const tq2 = await chargeAnswerTokens(env, bucketId, stats, messageChars(messages), full.length);
|
|
2879
|
+
send({ type: "quota", quota: { ...quota, ...tq2 } });
|
|
2845
2880
|
const canonical0 = canonicalRefusal(full);
|
|
2846
2881
|
const c2 = canonical0.includes(refusalAnswer()) ? { text: canonical0, blocks: [], dropped: [] } : await contractV2(env.DB, canonical0, usedHits);
|
|
2847
2882
|
send({
|
|
@@ -3007,7 +3042,8 @@ ${q.query}` }],
|
|
|
3007
3042
|
text: h.text.slice(0, 1200),
|
|
3008
3043
|
...h.metadata.table_selection ? { sel: h.metadata.table_selection } : {}
|
|
3009
3044
|
}));
|
|
3010
|
-
|
|
3045
|
+
const tq = await chargeAnswerTokens(env, bucketId, void 0, messageChars(messages), answer.length);
|
|
3046
|
+
return json({ ...out, context: contextOut, read: readAs(), quota: { ...quota, ...tq } }, 200, { ...corsHeaders(req), "server-timing": serverTiming() });
|
|
3011
3047
|
}
|
|
3012
3048
|
function sseResponse(events, cors) {
|
|
3013
3049
|
const encoder = new TextEncoder();
|
package/dist/quota.d.ts
CHANGED
|
@@ -15,3 +15,11 @@ export declare function telemetry(env: Env, ctx: Background, tier: string, route
|
|
|
15
15
|
keyId?: string | null;
|
|
16
16
|
retries?: number;
|
|
17
17
|
}): void;
|
|
18
|
+
export declare function estimateTokens(chars: number): number;
|
|
19
|
+
export declare function tokenLimit(env: Env, bucketId: string): number;
|
|
20
|
+
export declare function tokenBudget(env: Env, bucketId: string): Promise<{
|
|
21
|
+
used: number;
|
|
22
|
+
limit: number;
|
|
23
|
+
}>;
|
|
24
|
+
export declare function chargeTokens(env: Env, bucketId: string, tokens: number): Promise<void>;
|
|
25
|
+
export declare function usageTotal(usage: unknown): number | null;
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
modelGroundingBlock,
|
|
7
7
|
scoreFaithfulness,
|
|
8
8
|
scoreJudge
|
|
9
|
-
} from "../../chunk-
|
|
9
|
+
} from "../../chunk-V7PEVNNO.js";
|
|
10
10
|
import {
|
|
11
11
|
handleSearch
|
|
12
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-OXTRNMTZ.js";
|
|
13
13
|
import {
|
|
14
14
|
buildMessages,
|
|
15
15
|
cfBlobs,
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
sessionFrom,
|
|
36
36
|
telemetry,
|
|
37
37
|
understandQuery
|
|
38
|
-
} from "../../chunk-
|
|
38
|
+
} from "../../chunk-LL3LWBZH.js";
|
|
39
39
|
import {
|
|
40
40
|
authenticate,
|
|
41
41
|
corsHeaders,
|
|
@@ -959,7 +959,7 @@ async function handleMcp(env, ctx, req, tier, key) {
|
|
|
959
959
|
// stream:false forces the JSON lane (anon defaults to SSE)
|
|
960
960
|
body: JSON.stringify({ ...args, stream: false })
|
|
961
961
|
});
|
|
962
|
-
const res = name === "ask" ? await (await import("../../ask-
|
|
962
|
+
const res = name === "ask" ? await (await import("../../ask-MFFOHVQ2.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-LN5Q7OSZ.js")).handleSearch(env, ctx, inner, tier, key);
|
|
963
963
|
return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
|
|
964
964
|
});
|
|
965
965
|
if (out.ok && "accepted" in out) return new Response(null, { status: 202 });
|
package/package.json
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"playwright": "^1.62.1",
|
|
28
28
|
"yaml": "^2.9.1"
|
|
29
29
|
},
|
|
30
|
-
"version": "0.2.
|
|
30
|
+
"version": "0.2.73",
|
|
31
31
|
"description": "The Konneal engine: the publisher-agnostic build pipeline and API plane for standards intelligence (retrieval, answer contract, verdicts, evaluation).",
|
|
32
32
|
"license": "BSD-3-Clause",
|
|
33
33
|
"type": "module",
|
|
@@ -37,7 +37,7 @@ import { cacheKeyMaterial, corpusGen, exactCacheKey, freshRequested } from "./an
|
|
|
37
37
|
import type { Env } from "./env";
|
|
38
38
|
export type { Env };
|
|
39
39
|
import { json, err, corsHeaders, readJson, validateQuery, type ApiKey } from "./lib/http";
|
|
40
|
-
import {
|
|
40
|
+
import { chargeTokens, checkQuota, clientIp, estimateTokens, telemetry, tokenBudget, usageTotal } from "./quota";
|
|
41
41
|
import { graphExpand, editionNote } from "./graph";
|
|
42
42
|
import { registerNote, searchRegister, certificateLinks } from "./certificates";
|
|
43
43
|
import { TOOL_DECLARATION, parseToolCall, runTool, toolNote } from "./tools";
|
|
@@ -206,7 +206,28 @@ async function summarizeHistory(
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
|
|
209
|
+
/** The prompt's character weight — text parts only, image data URLs
|
|
210
|
+
* excluded (a photo must not read as half a million characters). */
|
|
211
|
+
function messageChars(messages: any[]): number {
|
|
212
|
+
let n = 0;
|
|
213
|
+
for (const m of messages) {
|
|
214
|
+
if (typeof m.content === "string") n += m.content.length;
|
|
215
|
+
else if (Array.isArray(m.content)) for (const part of m.content) if (part?.type === "text" && typeof part.text === "string") n += part.text.length;
|
|
216
|
+
}
|
|
217
|
+
return n;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Post-generation true-up: the model's own usage when the stream
|
|
221
|
+
* reported it, the character estimate otherwise — charged, then the
|
|
222
|
+
* budget's fresh state returned for the quota payload. */
|
|
223
|
+
async function chargeAnswerTokens(env: Env, bucketId: string, stats: { usage?: unknown } | undefined, promptChars: number, answerChars: number): Promise<{ tokens_used: number; token_limit: number }> {
|
|
224
|
+
const total = usageTotal(stats?.usage) ?? estimateTokens(promptChars + answerChars);
|
|
225
|
+
await chargeTokens(env, bucketId, total);
|
|
226
|
+
const budget = await tokenBudget(env, bucketId);
|
|
227
|
+
return { tokens_used: budget.used, token_limit: budget.limit };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function* sseTokens(stream: ReadableStream<Uint8Array>, stats?: { usage?: unknown }): AsyncGenerator<string> {
|
|
210
231
|
const reader = stream.getReader();
|
|
211
232
|
const decoder = new TextDecoder();
|
|
212
233
|
let buf = "";
|
|
@@ -227,6 +248,7 @@ async function* sseTokens(stream: ReadableStream<Uint8Array>): AsyncGenerator<st
|
|
|
227
248
|
typeof evt?.response === "string"
|
|
228
249
|
? evt.response
|
|
229
250
|
: evt?.choices?.[0]?.delta?.content;
|
|
251
|
+
if (evt.usage && stats && stats.usage === undefined) stats.usage = evt.usage;
|
|
230
252
|
if (tok) yield tok;
|
|
231
253
|
} catch {
|
|
232
254
|
// partial JSON in line splitting — ignore
|
|
@@ -320,6 +342,14 @@ async function handleAsk(
|
|
|
320
342
|
if (!quota.ok) {
|
|
321
343
|
return err(429, "quota_exceeded", `Daily question limit reached (${quota.limit}). Try again tomorrow.`);
|
|
322
344
|
}
|
|
345
|
+
// the real allowance is the day's TOKENS (the 2026-09-29 shift): the
|
|
346
|
+
// request cap above is the anti-abuse backstop, this is the budget
|
|
347
|
+
const tokBudget = await tokenBudget(env, bucketId);
|
|
348
|
+
if (tokBudget.used >= tokBudget.limit) {
|
|
349
|
+
return err(429, "quota_exceeded", `Daily token budget reached (${tokBudget.limit} tokens). Try again tomorrow.`);
|
|
350
|
+
}
|
|
351
|
+
(quota as any).tokens_used = tokBudget.used;
|
|
352
|
+
(quota as any).token_limit = tokBudget.limit;
|
|
323
353
|
|
|
324
354
|
const hardCap = num(env as any, "ANON_DAY_HARD_CAP", 5000);
|
|
325
355
|
if (tier === "anon" && quota.used > hardCap) {
|
|
@@ -668,15 +698,18 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
668
698
|
// the reading arrives first — a conversational turn is still a reading
|
|
669
699
|
if (readAs()) send({ type: "read", read: readAs() });
|
|
670
700
|
send({ type: "citations", citations: [], context_applied: NO_CONTEXT, quota, });
|
|
701
|
+
const stats: { usage?: unknown } = {};
|
|
671
702
|
let full = "";
|
|
672
703
|
try {
|
|
673
|
-
for await (const tok of sseTokens(stream)) {
|
|
704
|
+
for await (const tok of sseTokens(stream, stats)) {
|
|
674
705
|
full += tok;
|
|
675
706
|
send({ type: "token", v: tok });
|
|
676
707
|
}
|
|
677
708
|
} catch {
|
|
678
709
|
// stream ended prematurely — deliver what we have
|
|
679
710
|
}
|
|
711
|
+
const tq = await chargeAnswerTokens(env, bucketId, stats, messageChars(messages), full.length);
|
|
712
|
+
send({ type: "quota", quota: { ...quota, ...tq } });
|
|
680
713
|
send({ type: "done", model, query_hash: queryHash, context_applied: NO_CONTEXT, read: readAs() });
|
|
681
714
|
telemetry(env, ctx, tier, "ask", model, true, full.length, queryHash, q.lang, undefined, telemetryMeta());
|
|
682
715
|
controller.close();
|
|
@@ -748,7 +781,8 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
748
781
|
corsHeaders(req),
|
|
749
782
|
);
|
|
750
783
|
}
|
|
751
|
-
|
|
784
|
+
const tq = await chargeAnswerTokens(env, bucketId, undefined, q.query.length, verdict.answer.length);
|
|
785
|
+
return json({ answer: verdict.answer, citations, model, query_hash: queryHash, follow_ups: [], context_applied: draftCtxApplied, read: readAs(), ...(draftPayload ? { draft: draftPayload } : {}), quota: { ...quota, ...tq } });
|
|
752
786
|
}
|
|
753
787
|
|
|
754
788
|
// ── The api_call draft (TODO.ai-platform/09) — the operations
|
|
@@ -785,7 +819,8 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
785
819
|
corsHeaders(req),
|
|
786
820
|
);
|
|
787
821
|
}
|
|
788
|
-
|
|
822
|
+
const tq = await chargeAnswerTokens(env, bucketId, undefined, q.query.length, verdict.answer.length);
|
|
823
|
+
return json({ answer: verdict.answer, citations: [], model, query_hash: queryHash, follow_ups: [], context_applied: callCtxApplied, read: readAs(), ...(draftPayload ? { draft: draftPayload } : {}), quota: { ...quota, ...tq } });
|
|
789
824
|
}
|
|
790
825
|
|
|
791
826
|
// (declared before the retrieval try: the account block, the refusal
|
|
@@ -1227,15 +1262,18 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1227
1262
|
send({ type: "read", read: readAs() });
|
|
1228
1263
|
const sourceQuality = answerQuality(cites.map((c: any) => c.quality));
|
|
1229
1264
|
send({ type: "citations", citations: cites, context_applied: ctxApplied, ...(sourceQuality ? { source_quality: sourceQuality, confidence_note: qualityNote(sourceQuality), ...(sourceQuality === "ocr" ? { experimental_sources: experimentalSourceLabels(cites) } : {}) } : {}), ...(liveRecords ? { records: liveRecords } : {}), quota, });
|
|
1265
|
+
const stats: { usage?: unknown } = {};
|
|
1230
1266
|
let full = "";
|
|
1231
1267
|
try {
|
|
1232
|
-
for await (const tok of sseTokens(stream)) {
|
|
1268
|
+
for await (const tok of sseTokens(stream, stats)) {
|
|
1233
1269
|
full += tok;
|
|
1234
1270
|
send({ type: "token", v: tok });
|
|
1235
1271
|
}
|
|
1236
1272
|
} catch {
|
|
1237
1273
|
// stream ended prematurely — deliver what we have
|
|
1238
1274
|
}
|
|
1275
|
+
const tq = await chargeAnswerTokens(env, bucketId, stats, messageChars(messages), full.length);
|
|
1276
|
+
send({ type: "quota", quota: { ...quota, ...tq } });
|
|
1239
1277
|
const canonical0 = canonicalRefusal(full);
|
|
1240
1278
|
// answer contract v2: validate [[u:]] refs, resolve typed blocks
|
|
1241
1279
|
const c2 = canonical0.includes(refusalAnswer())
|
|
@@ -1475,7 +1513,8 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1475
1513
|
text: h.text.slice(0, 1200),
|
|
1476
1514
|
...((h.metadata as any).table_selection ? { sel: (h.metadata as any).table_selection } : {}),
|
|
1477
1515
|
}));
|
|
1478
|
-
|
|
1516
|
+
const tq = await chargeAnswerTokens(env, bucketId, undefined, messageChars(messages), answer.length);
|
|
1517
|
+
return json({ ...out, context: contextOut, read: readAs(), quota: { ...quota, ...tq } }, 200, { ...corsHeaders(req), "server-timing": serverTiming() });
|
|
1479
1518
|
}
|
|
1480
1519
|
|
|
1481
1520
|
function sseResponse(events: unknown[], cors: Record<string, string>): Response {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Quota + telemetry: per-bucket daily counters (KV) and the D1
|
|
2
2
|
// queries/spend ledger writes (TODO.impl/23).
|
|
3
|
-
import { sha256Hex, today } from "./config";
|
|
3
|
+
import { num, sha256Hex, today } from "./config.ts";
|
|
4
4
|
import type { Env } from "./env";
|
|
5
5
|
import type { Background } from "./ports/runtime.ts";
|
|
6
6
|
|
|
@@ -54,3 +54,44 @@ export function telemetry(
|
|
|
54
54
|
]),
|
|
55
55
|
);
|
|
56
56
|
}
|
|
57
|
+
|
|
58
|
+
// ── the token budget (2026-09-29): the daily allowance is TOKENS, not
|
|
59
|
+
// questions — an elevated-effort answer over a wide retrieval context
|
|
60
|
+
// costs orders of magnitude more than a cached refusal, and one number
|
|
61
|
+
// should say so. Charged after generation from the model's own usage
|
|
62
|
+
// when the stream reports it, estimated by characters otherwise.
|
|
63
|
+
export function estimateTokens(chars: number): number {
|
|
64
|
+
return Math.ceil(chars / 4);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function tokenTier(bucketId: string): "anon" | "member" | "key" {
|
|
68
|
+
return bucketId.startsWith("key:") ? "key" : bucketId.startsWith("sub:") ? "member" : "anon";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function tokenLimit(env: Env, bucketId: string): number {
|
|
72
|
+
const tier = tokenTier(bucketId);
|
|
73
|
+
return tier === "key"
|
|
74
|
+
? num(env as any, "KEY_DAY_TOKENS", 4_000_000)
|
|
75
|
+
: tier === "member"
|
|
76
|
+
? num(env as any, "MEMBER_DAY_TOKENS", 1_500_000)
|
|
77
|
+
: num(env as any, "ANON_DAY_TOKENS", 300_000);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function tokenBudget(env: Env, bucketId: string): Promise<{ used: number; limit: number }> {
|
|
81
|
+
const used = Number((await env.CACHE.get(`t:${today()}:ask:${await sha256Hex(bucketId)}`)) ?? "0");
|
|
82
|
+
return { used, limit: tokenLimit(env, bucketId) };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function chargeTokens(env: Env, bucketId: string, tokens: number): Promise<void> {
|
|
86
|
+
if (!(tokens > 0)) return;
|
|
87
|
+
await kvIncr(env.CACHE, `t:${today()}:ask:${await sha256Hex(bucketId)}`, tokens);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function usageTotal(usage: unknown): number | null {
|
|
91
|
+
if (!usage || typeof usage !== "object") return null;
|
|
92
|
+
const u = usage as any;
|
|
93
|
+
const p = Number(u.prompt_tokens ?? 0);
|
|
94
|
+
const c = Number(u.completion_tokens ?? 0);
|
|
95
|
+
const total = p + c;
|
|
96
|
+
return Number.isFinite(total) && total > 0 ? total : null;
|
|
97
|
+
}
|