@konneal/engine 0.2.85 → 0.2.86
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-52JUHGUG.js → ask-XM5FO6LR.js} +2 -1
- package/dist/chunk-GCK6DW7E.js +234 -0
- package/dist/{chunk-R3KA6S3E.js → chunk-PIEUN5B2.js} +10 -116
- package/dist/tools-3AMOGBJG.js +14 -0
- package/dist/tools-content.d.ts +5 -0
- package/dist/tools.d.ts +2 -2
- package/dist/worker_public/src/index.js +10 -3
- package/package.json +1 -1
- package/workers/worker_public/src/ask.ts +1 -1
- package/workers/worker_public/src/mcp.ts +7 -0
- package/workers/worker_public/src/tools-content.ts +139 -0
- package/workers/worker_public/src/tools.ts +8 -6
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// workers/worker_public/src/refs.ts
|
|
2
|
+
var REF = /\[\[(u:[A-Za-z0-9_-]+)\]\]/g;
|
|
3
|
+
function availableUnitIds(hits) {
|
|
4
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5
|
+
for (const h of hits) {
|
|
6
|
+
const u = h.metadata?.unit_id;
|
|
7
|
+
if (u) ids.add(u);
|
|
8
|
+
}
|
|
9
|
+
return ids;
|
|
10
|
+
}
|
|
11
|
+
function parseRefs(text) {
|
|
12
|
+
return [...text.matchAll(REF)].map((m) => m[1]);
|
|
13
|
+
}
|
|
14
|
+
function sanitizeRefs(text, available) {
|
|
15
|
+
const dropped = [];
|
|
16
|
+
const out = text.replace(REF, (full, id) => {
|
|
17
|
+
if (available.has(id)) return full;
|
|
18
|
+
dropped.push(id);
|
|
19
|
+
return "";
|
|
20
|
+
});
|
|
21
|
+
return { text: out, dropped };
|
|
22
|
+
}
|
|
23
|
+
async function resolveBlocks(db, refs) {
|
|
24
|
+
if (!refs.length) return [];
|
|
25
|
+
const uniq = [...new Set(refs)].slice(0, 12);
|
|
26
|
+
const blocks = [];
|
|
27
|
+
for (let i = 0; i < uniq.length; i += 20) {
|
|
28
|
+
const batch = uniq.slice(i, i + 20);
|
|
29
|
+
const placeholders = batch.map((_, n) => `?${n + 1}`).join(",");
|
|
30
|
+
try {
|
|
31
|
+
const res = await db.prepare(`SELECT unit_id, type, docidentifier, edition, payload FROM unit_payloads WHERE unit_id IN (${placeholders})`).bind(...batch).all();
|
|
32
|
+
for (const r of res.results) {
|
|
33
|
+
let payload = {};
|
|
34
|
+
try {
|
|
35
|
+
payload = JSON.parse(String(r.payload));
|
|
36
|
+
} catch {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
blocks.push({
|
|
40
|
+
unit_id: String(r.unit_id),
|
|
41
|
+
type: String(r.type),
|
|
42
|
+
docidentifier: String(r.docidentifier ?? ""),
|
|
43
|
+
edition: r.edition ? String(r.edition) : void 0,
|
|
44
|
+
payload
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.log("resolveBlocks failed:", String(e).slice(0, 150));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return blocks;
|
|
52
|
+
}
|
|
53
|
+
async function contractV2(db, answer, usedHits) {
|
|
54
|
+
const available = availableUnitIds(usedHits);
|
|
55
|
+
const { text, dropped } = sanitizeRefs(answer, available);
|
|
56
|
+
if (dropped.length) console.log("refs: dropped", dropped.length, "invalid (not in passages)");
|
|
57
|
+
const refs = parseRefs(text);
|
|
58
|
+
const blocks = await resolveBlocks(db, refs);
|
|
59
|
+
return { text, blocks, dropped };
|
|
60
|
+
}
|
|
61
|
+
function tableRetyped(text, availableTable) {
|
|
62
|
+
if (!availableTable) return false;
|
|
63
|
+
return /(^|\n)\s*\|[^\n]+\|\s*(\n\s*\|[-: |]+\|\s*)?(\n|$)/.test(text) && (text.match(/\|/g) ?? []).length >= 6;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// workers/worker_public/src/tools-content.ts
|
|
67
|
+
var slug = (docidentifier) => docidentifier.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
68
|
+
var unitsGet = {
|
|
69
|
+
name: "units.get",
|
|
70
|
+
description: "Fetch typed Recommendation content \u2014 a table, formula or figure as structured data (columns, rows, MathML, caption), by its unit id. The machine-consumable form of a Recommendation's content, for computing with rather than reading.",
|
|
71
|
+
params: [{ key: "unit_id", required: true, description: "the unit id, e.g. u:table-1 or u:form-3 (ids appear in answers' [[u:\u2026]] references)" }],
|
|
72
|
+
audiences: ["mcp"],
|
|
73
|
+
handler: async (env, args) => {
|
|
74
|
+
const unit_id = String(args?.unit_id ?? "").trim().replace(/^\[?\[?u:/, "u:").replace(/]?\]?$/, "");
|
|
75
|
+
if (!/^u:[\w.-]+$/.test(unit_id)) return null;
|
|
76
|
+
const blocks = await resolveBlocks(env.DB, [unit_id]);
|
|
77
|
+
if (!blocks.length) {
|
|
78
|
+
return { name: "units.get", query: unit_id, output: `No typed unit "${unit_id}" exists in the indexed payloads. State this plainly.` };
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
name: "units.get",
|
|
82
|
+
query: unit_id,
|
|
83
|
+
output: JSON.stringify(
|
|
84
|
+
blocks.map((b) => ({ unit_id: b.unit_id, type: b.type, docidentifier: b.docidentifier, payload: b.payload }))
|
|
85
|
+
)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var graphCites = {
|
|
90
|
+
name: "graph.cites",
|
|
91
|
+
description: "The bibliography edges: what a publication's own bibliography actually cites, extracted from its indexed bibliography sections. Returns the cited works' labels for a document (optionally scoped to an edition).",
|
|
92
|
+
params: [
|
|
93
|
+
{ key: "doc", required: true, description: "the publication identifier, e.g. R 60-1:2021" },
|
|
94
|
+
{ key: "edition", required: false, description: "an edition year to scope, e.g. 2021" }
|
|
95
|
+
],
|
|
96
|
+
audiences: ["mcp"],
|
|
97
|
+
handler: async (env, args) => {
|
|
98
|
+
const doc = String(args?.doc ?? "").trim();
|
|
99
|
+
if (doc.length < 3) return null;
|
|
100
|
+
const edition = args?.edition ? String(args.edition).trim() : null;
|
|
101
|
+
const pat = `%${doc.replace(/[:\s]+/g, "%")}%${edition ? edition : ""}%`;
|
|
102
|
+
try {
|
|
103
|
+
const rows = await env.DB.prepare(
|
|
104
|
+
"SELECT DISTINCT n.label AS label FROM graph_edges e JOIN documents d ON e.src = d.canonical_id JOIN graph_nodes n ON e.dst = n.id WHERE d.docidentifier LIKE ?1 AND e.kind = 'cites' ORDER BY label LIMIT 60"
|
|
105
|
+
).bind(pat).all();
|
|
106
|
+
const labels = (rows.results ?? []).map((r) => r.label).filter(Boolean);
|
|
107
|
+
return {
|
|
108
|
+
name: "graph.cites",
|
|
109
|
+
query: `${doc}${edition ? ` (${edition})` : ""}`,
|
|
110
|
+
output: labels.length ? `The publication's bibliography cites:
|
|
111
|
+
${labels.map((l) => `- ${l}`).join("\n")}` : `No bibliography citations are indexed for "${doc}"${edition ? ` (${edition})` : ""}. State this plainly.`
|
|
112
|
+
};
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var docsSection = {
|
|
119
|
+
name: "docs.section",
|
|
120
|
+
description: "Fetch one clause of a publication's rendered document \u2014 its text and its permanent deep-link URL \u2014 for agents that need the human-formatted passage with its anchor.",
|
|
121
|
+
params: [
|
|
122
|
+
{ key: "doc", required: true, description: "the publication identifier, e.g. R 60-1:2021" },
|
|
123
|
+
{ key: "clause", required: true, description: "the clause anchor, e.g. 5.1.1 or annex-b" }
|
|
124
|
+
],
|
|
125
|
+
audiences: ["mcp"],
|
|
126
|
+
handler: async (env, args) => {
|
|
127
|
+
const doc = String(args?.doc ?? "").trim();
|
|
128
|
+
const clause = String(args?.clause ?? "").trim();
|
|
129
|
+
if (doc.length < 3 || !clause) return null;
|
|
130
|
+
try {
|
|
131
|
+
const rows = await env.DB.prepare(
|
|
132
|
+
"SELECT docidentifier, clause_anchor, clause_title, text FROM chunks WHERE docidentifier LIKE ?1 AND REPLACE(clause_anchor, ' ', '') = ?2 LIMIT 1"
|
|
133
|
+
).bind(`%${doc.replace(/[:\s]+/g, "%")}%`, clause).all();
|
|
134
|
+
const r = (rows.results ?? [])[0];
|
|
135
|
+
if (!r) {
|
|
136
|
+
return { name: "docs.section", query: `${doc} \xA7${clause}`, output: `No clause "${clause}" is indexed for "${doc}". State this plainly.` };
|
|
137
|
+
}
|
|
138
|
+
const url = `https://www.ommisa.org/docs/${slug(r.docidentifier)}.html#${r.clause_anchor}`;
|
|
139
|
+
return {
|
|
140
|
+
name: "docs.section",
|
|
141
|
+
query: `${r.docidentifier} \xA7${r.clause_anchor}`,
|
|
142
|
+
output: JSON.stringify({ docidentifier: r.docidentifier, clause: r.clause_anchor, title: r.clause_title, url, text: (r.text ?? "").slice(0, 4e3) })
|
|
143
|
+
};
|
|
144
|
+
} catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
var glossaryLookup = {
|
|
150
|
+
name: "glossary.lookup",
|
|
151
|
+
description: "Look up a defined term in the terminology datasets \u2014 the concept's definition and its defining publication. Bind everyday words to the defined term before interpreting a question.",
|
|
152
|
+
params: [{ key: "term", required: true, description: "the term or everyday phrase to bind, e.g. creep or load cell" }],
|
|
153
|
+
audiences: ["mcp"],
|
|
154
|
+
handler: async (env, args) => {
|
|
155
|
+
const term = String(args?.term ?? "").trim();
|
|
156
|
+
if (term.length < 2) return null;
|
|
157
|
+
try {
|
|
158
|
+
const rows = await env.DB.prepare(
|
|
159
|
+
"SELECT clause_title AS term, chunk_text AS text, docidentifier FROM chunks WHERE corpus = 'glossary' AND (clause_title = ?1 OR clause_title LIKE ?2) LIMIT 3"
|
|
160
|
+
).bind(term, `%${term}%`).all();
|
|
161
|
+
const hits = (rows.results ?? []).filter((r) => r.term);
|
|
162
|
+
if (!hits.length) {
|
|
163
|
+
return { name: "glossary.lookup", query: term, output: `No defined term matches "${term}" in the terminology datasets. State this plainly.` };
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
name: "glossary.lookup",
|
|
167
|
+
query: term,
|
|
168
|
+
output: hits.map((r) => `- ${r.term} (${r.docidentifier}): ${String(r.text ?? "").split(" \u2014 ").slice(1).join(" \u2014 ").slice(0, 400)}`).join("\n")
|
|
169
|
+
};
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// workers/worker_public/src/tools.ts
|
|
177
|
+
function parseToolCall(text) {
|
|
178
|
+
const m = /^TOOL\s+([a-z_.]+)\s*(\{[^\n}]*\})?/m.exec(String(text ?? ""));
|
|
179
|
+
if (!m) return null;
|
|
180
|
+
let args = {};
|
|
181
|
+
if (m[2]) {
|
|
182
|
+
try {
|
|
183
|
+
args = JSON.parse(m[2]);
|
|
184
|
+
} catch {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
} else if (/^TOOL\s+[a-z_.]+\s*\{/m.test(String(text ?? ""))) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
return { name: m[1].toLowerCase(), args };
|
|
191
|
+
}
|
|
192
|
+
var certificatesSearch = {
|
|
193
|
+
name: "certificates.search",
|
|
194
|
+
description: "Search the certificate register (a snapshot) by holder name, model designation, or a printed certificate number. Returns the matching rows \u2014 number, holder, model, issue year, status, and document links where on file \u2014 or the exact no-match statement for the string asked.",
|
|
195
|
+
params: [{ key: "query", required: true, description: "the holder, model, or printed certificate number to look up" }],
|
|
196
|
+
audiences: ["agent", "mcp"],
|
|
197
|
+
handler: async (env, args) => {
|
|
198
|
+
const query = String(args?.query ?? "").trim().slice(0, 160);
|
|
199
|
+
if (!query) return null;
|
|
200
|
+
const { searchRegister, registerNote, certificateLinks } = await import("./certificates-CO7KX2S2.js");
|
|
201
|
+
const reg = await searchRegister(env.DB, query, true);
|
|
202
|
+
const output = reg?.rows?.length ? [registerNote(reg.rows), certificateLinks(reg.rows)].filter(Boolean).join("\n") : `No certificate was found for "${query}" in the certificates database (the register snapshot). State this as the search's result, with the searched string visible.`;
|
|
203
|
+
return { name: "certificates.search", query, output };
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
var TOOLS_REGISTRY = [certificatesSearch, unitsGet, graphCites, docsSection, glossaryLookup];
|
|
207
|
+
var TOOL_DECLARATION = [
|
|
208
|
+
"You may use one tool before answering, by writing a single line:",
|
|
209
|
+
...TOOLS_REGISTRY.filter((t) => t.audiences.includes("agent")).map((t) => {
|
|
210
|
+
const shape = `{${t.params.map((p) => `"${p.key}": "<${p.description}>"`).join(", ")}}`;
|
|
211
|
+
return `TOOL ${t.name} ${shape}`;
|
|
212
|
+
}),
|
|
213
|
+
"The worker runs it and returns the result attributed \u2014 phrase the tool's result as what it returned, with the searched string visible in your answer. Use a tool when the question turns on what it answers (including from a photograph). If you do not need it, answer directly without the line."
|
|
214
|
+
].join("\n");
|
|
215
|
+
async function runTool(env, call) {
|
|
216
|
+
const spec = TOOLS_REGISTRY.find((t) => t.name === call.name && t.audiences.includes("agent"));
|
|
217
|
+
if (!spec) return null;
|
|
218
|
+
return spec.handler(env, call.args ?? {});
|
|
219
|
+
}
|
|
220
|
+
function toolNote(r) {
|
|
221
|
+
return `The ${r.name} tool returned, for the query "${r.query}":
|
|
222
|
+
${r.output}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export {
|
|
226
|
+
resolveBlocks,
|
|
227
|
+
contractV2,
|
|
228
|
+
tableRetyped,
|
|
229
|
+
parseToolCall,
|
|
230
|
+
TOOLS_REGISTRY,
|
|
231
|
+
TOOL_DECLARATION,
|
|
232
|
+
runTool,
|
|
233
|
+
toolNote
|
|
234
|
+
};
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TOOL_DECLARATION,
|
|
3
|
+
contractV2,
|
|
4
|
+
parseToolCall,
|
|
5
|
+
resolveBlocks,
|
|
6
|
+
runTool,
|
|
7
|
+
tableRetyped,
|
|
8
|
+
toolNote
|
|
9
|
+
} from "./chunk-GCK6DW7E.js";
|
|
1
10
|
import {
|
|
2
11
|
NO_CONTEXT,
|
|
3
12
|
answerQuality,
|
|
@@ -363,71 +372,6 @@ function checkQuoteAnchors(answer, passages) {
|
|
|
363
372
|
}
|
|
364
373
|
var ANCHOR_CORRECTION_NOTE = "Correction notice: your draft quoted text that does not appear verbatim in the provided passages. Rewrite the answer \u2014 every quoted phrase must be an exact copy from a passage, or cite the clause without quoting.";
|
|
365
374
|
|
|
366
|
-
// workers/worker_public/src/refs.ts
|
|
367
|
-
var REF = /\[\[(u:[A-Za-z0-9_-]+)\]\]/g;
|
|
368
|
-
function availableUnitIds(hits) {
|
|
369
|
-
const ids = /* @__PURE__ */ new Set();
|
|
370
|
-
for (const h of hits) {
|
|
371
|
-
const u = h.metadata?.unit_id;
|
|
372
|
-
if (u) ids.add(u);
|
|
373
|
-
}
|
|
374
|
-
return ids;
|
|
375
|
-
}
|
|
376
|
-
function parseRefs(text) {
|
|
377
|
-
return [...text.matchAll(REF)].map((m) => m[1]);
|
|
378
|
-
}
|
|
379
|
-
function sanitizeRefs(text, available) {
|
|
380
|
-
const dropped = [];
|
|
381
|
-
const out = text.replace(REF, (full, id) => {
|
|
382
|
-
if (available.has(id)) return full;
|
|
383
|
-
dropped.push(id);
|
|
384
|
-
return "";
|
|
385
|
-
});
|
|
386
|
-
return { text: out, dropped };
|
|
387
|
-
}
|
|
388
|
-
async function resolveBlocks(db, refs) {
|
|
389
|
-
if (!refs.length) return [];
|
|
390
|
-
const uniq = [...new Set(refs)].slice(0, 12);
|
|
391
|
-
const blocks = [];
|
|
392
|
-
for (let i = 0; i < uniq.length; i += 20) {
|
|
393
|
-
const batch = uniq.slice(i, i + 20);
|
|
394
|
-
const placeholders = batch.map((_, n) => `?${n + 1}`).join(",");
|
|
395
|
-
try {
|
|
396
|
-
const res = await db.prepare(`SELECT unit_id, type, docidentifier, edition, payload FROM unit_payloads WHERE unit_id IN (${placeholders})`).bind(...batch).all();
|
|
397
|
-
for (const r of res.results) {
|
|
398
|
-
let payload = {};
|
|
399
|
-
try {
|
|
400
|
-
payload = JSON.parse(String(r.payload));
|
|
401
|
-
} catch {
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
blocks.push({
|
|
405
|
-
unit_id: String(r.unit_id),
|
|
406
|
-
type: String(r.type),
|
|
407
|
-
docidentifier: String(r.docidentifier ?? ""),
|
|
408
|
-
edition: r.edition ? String(r.edition) : void 0,
|
|
409
|
-
payload
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
} catch (e) {
|
|
413
|
-
console.log("resolveBlocks failed:", String(e).slice(0, 150));
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
return blocks;
|
|
417
|
-
}
|
|
418
|
-
async function contractV2(db, answer, usedHits) {
|
|
419
|
-
const available = availableUnitIds(usedHits);
|
|
420
|
-
const { text, dropped } = sanitizeRefs(answer, available);
|
|
421
|
-
if (dropped.length) console.log("refs: dropped", dropped.length, "invalid (not in passages)");
|
|
422
|
-
const refs = parseRefs(text);
|
|
423
|
-
const blocks = await resolveBlocks(db, refs);
|
|
424
|
-
return { text, blocks, dropped };
|
|
425
|
-
}
|
|
426
|
-
function tableRetyped(text, availableTable) {
|
|
427
|
-
if (!availableTable) return false;
|
|
428
|
-
return /(^|\n)\s*\|[^\n]+\|\s*(\n\s*\|[-: |]+\|\s*)?(\n|$)/.test(text) && (text.match(/\|/g) ?? []).length >= 6;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
375
|
// workers/worker_public/src/completion.ts
|
|
432
376
|
async function completeTables(db, answer, used) {
|
|
433
377
|
const blocks = [];
|
|
@@ -1873,55 +1817,6 @@ function exactCacheKey(indexVersion, gen, ns, queryHash) {
|
|
|
1873
1817
|
return `a:${indexVersion}:g${gen}:${ns}:${queryHash}`;
|
|
1874
1818
|
}
|
|
1875
1819
|
|
|
1876
|
-
// workers/worker_public/src/tools.ts
|
|
1877
|
-
function parseToolCall(text) {
|
|
1878
|
-
const m = /^TOOL\s+([a-z_.]+)\s*(\{[^\n}]*\})?/m.exec(String(text ?? ""));
|
|
1879
|
-
if (!m) return null;
|
|
1880
|
-
let args = {};
|
|
1881
|
-
if (m[2]) {
|
|
1882
|
-
try {
|
|
1883
|
-
args = JSON.parse(m[2]);
|
|
1884
|
-
} catch {
|
|
1885
|
-
return null;
|
|
1886
|
-
}
|
|
1887
|
-
} else if (/^TOOL\s+[a-z_.]+\s*\{/m.test(String(text ?? ""))) {
|
|
1888
|
-
return null;
|
|
1889
|
-
}
|
|
1890
|
-
return { name: m[1].toLowerCase(), args };
|
|
1891
|
-
}
|
|
1892
|
-
var certificatesSearch = {
|
|
1893
|
-
name: "certificates.search",
|
|
1894
|
-
description: "Search the certificate register (a snapshot) by holder name, model designation, or a printed certificate number. Returns the matching rows \u2014 number, holder, model, issue year, status, and document links where on file \u2014 or the exact no-match statement for the string asked.",
|
|
1895
|
-
params: [{ key: "query", required: true, description: "the holder, model, or printed certificate number to look up" }],
|
|
1896
|
-
audiences: ["agent", "mcp"],
|
|
1897
|
-
handler: async (db, args) => {
|
|
1898
|
-
const query = String(args?.query ?? "").trim().slice(0, 160);
|
|
1899
|
-
if (!query) return null;
|
|
1900
|
-
const { searchRegister: searchRegister2, registerNote: registerNote2, certificateLinks: certificateLinks2 } = await import("./certificates-CO7KX2S2.js");
|
|
1901
|
-
const reg = await searchRegister2(db, query, true);
|
|
1902
|
-
const output = reg?.rows?.length ? [registerNote2(reg.rows), certificateLinks2(reg.rows)].filter(Boolean).join("\n") : `No certificate was found for "${query}" in the certificates database (the register snapshot). State this as the search's result, with the searched string visible.`;
|
|
1903
|
-
return { name: "certificates.search", query, output };
|
|
1904
|
-
}
|
|
1905
|
-
};
|
|
1906
|
-
var TOOLS_REGISTRY = [certificatesSearch];
|
|
1907
|
-
var TOOL_DECLARATION = [
|
|
1908
|
-
"You may use one tool before answering, by writing a single line:",
|
|
1909
|
-
...TOOLS_REGISTRY.filter((t) => t.audiences.includes("agent")).map((t) => {
|
|
1910
|
-
const shape = `{${t.params.map((p) => `"${p.key}": "<${p.description}>"`).join(", ")}}`;
|
|
1911
|
-
return `TOOL ${t.name} ${shape}`;
|
|
1912
|
-
}),
|
|
1913
|
-
"The worker runs it and returns the result attributed \u2014 phrase the tool's result as what it returned, with the searched string visible in your answer. Use a tool when the question turns on what it answers (including from a photograph). If you do not need it, answer directly without the line."
|
|
1914
|
-
].join("\n");
|
|
1915
|
-
async function runTool(db, call) {
|
|
1916
|
-
const spec = TOOLS_REGISTRY.find((t) => t.name === call.name && t.audiences.includes("agent"));
|
|
1917
|
-
if (!spec) return null;
|
|
1918
|
-
return spec.handler(db, call.args ?? {});
|
|
1919
|
-
}
|
|
1920
|
-
function toolNote(r) {
|
|
1921
|
-
return `The ${r.name} tool returned, for the query "${r.query}":
|
|
1922
|
-
${r.output}`;
|
|
1923
|
-
}
|
|
1924
|
-
|
|
1925
1820
|
// workers/worker_public/src/operations.ts
|
|
1926
1821
|
function isOperationIntent(query) {
|
|
1927
1822
|
return /\b(operations?|endpoints?)\b/i.test(query);
|
|
@@ -2916,7 +2811,7 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
|
|
|
2916
2811
|
const call = parseToolCall(probeText);
|
|
2917
2812
|
if (call) {
|
|
2918
2813
|
mark(`Searching the certificate register for \u201C${String(call.args?.query ?? "")}\u201D`);
|
|
2919
|
-
const result = await runTool(env
|
|
2814
|
+
const result = await runTool(env, call);
|
|
2920
2815
|
if (result) {
|
|
2921
2816
|
messages.push({ role: "system", content: toolNote(result) });
|
|
2922
2817
|
pendingTools.push(result);
|
|
@@ -3179,6 +3074,5 @@ export {
|
|
|
3179
3074
|
bindModelNode,
|
|
3180
3075
|
modelGroundingBlock,
|
|
3181
3076
|
scoreJudge,
|
|
3182
|
-
TOOLS_REGISTRY,
|
|
3183
3077
|
handleAsk
|
|
3184
3078
|
};
|
package/dist/tools.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface ToolSpec {
|
|
|
21
21
|
description: string;
|
|
22
22
|
}[];
|
|
23
23
|
audiences: ("agent" | "mcp")[];
|
|
24
|
-
handler: (
|
|
24
|
+
handler: (env: any, args: Record<string, unknown>) => Promise<ToolResult | null>;
|
|
25
25
|
}
|
|
26
26
|
export declare const TOOLS_REGISTRY: ToolSpec[];
|
|
27
27
|
/** The agent loop's protocol declaration, GENERATED from the registry —
|
|
@@ -29,7 +29,7 @@ export declare const TOOLS_REGISTRY: ToolSpec[];
|
|
|
29
29
|
* hand-maintained literal. */
|
|
30
30
|
export declare const TOOL_DECLARATION: string;
|
|
31
31
|
/** Dispatch by LOOKUP — a tool name is never hard-coded at a call site. */
|
|
32
|
-
export declare function runTool(
|
|
32
|
+
export declare function runTool(env: any, call: ToolCall): Promise<ToolResult | null>;
|
|
33
33
|
/** The attributed injection: the answer model sees what the tool
|
|
34
34
|
* returned, for the string it asked — never a standing claim. */
|
|
35
35
|
export declare function toolNote(r: ToolResult): string;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
TOOLS_REGISTRY,
|
|
3
2
|
bindModelNode,
|
|
4
3
|
checkQuoteAnchors,
|
|
5
4
|
handleAsk,
|
|
@@ -7,7 +6,10 @@ import {
|
|
|
7
6
|
modelGroundingBlock,
|
|
8
7
|
scoreFaithfulness,
|
|
9
8
|
scoreJudge
|
|
10
|
-
} from "../../chunk-
|
|
9
|
+
} from "../../chunk-PIEUN5B2.js";
|
|
10
|
+
import {
|
|
11
|
+
TOOLS_REGISTRY
|
|
12
|
+
} from "../../chunk-GCK6DW7E.js";
|
|
11
13
|
import {
|
|
12
14
|
handleSearch
|
|
13
15
|
} from "../../chunk-KO6DRI7C.js";
|
|
@@ -969,7 +971,12 @@ async function handleMcp(env, ctx, req, tier, key) {
|
|
|
969
971
|
// stream:false forces the JSON lane (anon defaults to SSE)
|
|
970
972
|
body: JSON.stringify({ ...args, stream: false })
|
|
971
973
|
});
|
|
972
|
-
const
|
|
974
|
+
const { runTool, TOOLS_REGISTRY: TOOLS_REGISTRY2 } = await import("../../tools-3AMOGBJG.js");
|
|
975
|
+
if (TOOLS_REGISTRY2.some((t) => t.name === name && t.audiences.includes("mcp"))) {
|
|
976
|
+
const r = await runTool({ ...env, DB: env.DB }, { name, args });
|
|
977
|
+
return r ? { tool: r.name, query: r.query, result: r.output } : { error: { message: `tool ${name} returned nothing for the given arguments` } };
|
|
978
|
+
}
|
|
979
|
+
const res = name === "ask" ? await (await import("../../ask-XM5FO6LR.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-LVUQLA3C.js")).handleSearch(env, ctx, inner, tier, key);
|
|
973
980
|
return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
|
|
974
981
|
});
|
|
975
982
|
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.86",
|
|
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",
|
|
@@ -1253,7 +1253,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1253
1253
|
const call = parseToolCall(probeText);
|
|
1254
1254
|
if (call) {
|
|
1255
1255
|
mark(`Searching the certificate register for “${String(call.args?.query ?? "")}”`);
|
|
1256
|
-
const result = await runTool(env
|
|
1256
|
+
const result = await runTool(env, call);
|
|
1257
1257
|
if (result) {
|
|
1258
1258
|
messages.push({ role: "system", content: toolNote(result) });
|
|
1259
1259
|
pendingTools.push(result);
|
|
@@ -32,6 +32,13 @@ export async function handleMcp(
|
|
|
32
32
|
});
|
|
33
33
|
// deferred so plain node can load mcp-proto without the handlers'
|
|
34
34
|
// .md prompt imports, which only the bundler resolves
|
|
35
|
+
// registry tools (certificates.search, units.get, …) dispatch through
|
|
36
|
+
// the SAME handlers the API would — the no-drift rule, now structural
|
|
37
|
+
const { runTool, TOOLS_REGISTRY } = await import("./tools.ts");
|
|
38
|
+
if (TOOLS_REGISTRY.some((t) => t.name === name && t.audiences.includes("mcp"))) {
|
|
39
|
+
const r = await runTool({ ...env, DB: (env as any).DB }, { name, args });
|
|
40
|
+
return r ? { tool: r.name, query: r.query, result: r.output } : { error: { message: `tool ${name} returned nothing for the given arguments` } };
|
|
41
|
+
}
|
|
35
42
|
const res = name === "ask"
|
|
36
43
|
? await (await import("./ask")).handleAsk(env, ctx as any, inner, tier, key)
|
|
37
44
|
: await (await import("./search")).handleSearch(env, ctx as any, inner, tier, key);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// The content-plane MCP tools (the owner's 2026-10-04 direction): a
|
|
2
|
+
// content plane earns a tool when its answer is MACHINE-SHAPED —
|
|
3
|
+
// structured data an agent computes with, not prose it re-reads. Every
|
|
4
|
+
// handler wraps the SAME query surface the API serves; entitlement
|
|
5
|
+
// scope rides the metadata the serving path already enforces. These are
|
|
6
|
+
// MCP-audience only: the agent path's declaration stays small because
|
|
7
|
+
// tool-selection quality degrades with menu size.
|
|
8
|
+
import type { ToolSpec } from "./tools.ts";
|
|
9
|
+
import { resolveBlocks } from "./refs.ts";
|
|
10
|
+
|
|
11
|
+
const slug = (docidentifier: string) =>
|
|
12
|
+
docidentifier
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
15
|
+
.replace(/^-+|-+$/g, "");
|
|
16
|
+
|
|
17
|
+
export const unitsGet: ToolSpec = {
|
|
18
|
+
name: "units.get",
|
|
19
|
+
description:
|
|
20
|
+
"Fetch typed Recommendation content — a table, formula or figure as structured data (columns, rows, MathML, caption), by its unit id. The machine-consumable form of a Recommendation's content, for computing with rather than reading.",
|
|
21
|
+
params: [{ key: "unit_id", required: true, description: "the unit id, e.g. u:table-1 or u:form-3 (ids appear in answers' [[u:…]] references)" }],
|
|
22
|
+
audiences: ["mcp"],
|
|
23
|
+
handler: async (env, args) => {
|
|
24
|
+
const unit_id = String(args?.unit_id ?? "").trim().replace(/^\[?\[?u:/, "u:").replace(/]?\]?$/, "");
|
|
25
|
+
if (!/^u:[\w.-]+$/.test(unit_id)) return null;
|
|
26
|
+
const blocks = await resolveBlocks(env.DB, [unit_id]);
|
|
27
|
+
if (!blocks.length) {
|
|
28
|
+
return { name: "units.get", query: unit_id, output: `No typed unit "${unit_id}" exists in the indexed payloads. State this plainly.` };
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
name: "units.get",
|
|
32
|
+
query: unit_id,
|
|
33
|
+
output: JSON.stringify(
|
|
34
|
+
blocks.map((b: any) => ({ unit_id: b.unit_id, type: b.type, docidentifier: b.docidentifier, payload: b.payload })),
|
|
35
|
+
),
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const graphCites: ToolSpec = {
|
|
41
|
+
name: "graph.cites",
|
|
42
|
+
description:
|
|
43
|
+
"The bibliography edges: what a publication's own bibliography actually cites, extracted from its indexed bibliography sections. Returns the cited works' labels for a document (optionally scoped to an edition).",
|
|
44
|
+
params: [
|
|
45
|
+
{ key: "doc", required: true, description: "the publication identifier, e.g. R 60-1:2021" },
|
|
46
|
+
{ key: "edition", required: false, description: "an edition year to scope, e.g. 2021" },
|
|
47
|
+
],
|
|
48
|
+
audiences: ["mcp"],
|
|
49
|
+
handler: async (env, args) => {
|
|
50
|
+
const doc = String(args?.doc ?? "").trim();
|
|
51
|
+
if (doc.length < 3) return null;
|
|
52
|
+
const edition = args?.edition ? String(args.edition).trim() : null;
|
|
53
|
+
const pat = `%${doc.replace(/[:\s]+/g, "%")}%${edition ? edition : ""}%`;
|
|
54
|
+
try {
|
|
55
|
+
const rows = await env.DB.prepare(
|
|
56
|
+
"SELECT DISTINCT n.label AS label FROM graph_edges e JOIN documents d ON e.src = d.canonical_id JOIN graph_nodes n ON e.dst = n.id WHERE d.docidentifier LIKE ?1 AND e.kind = 'cites' ORDER BY label LIMIT 60",
|
|
57
|
+
)
|
|
58
|
+
.bind(pat)
|
|
59
|
+
.all();
|
|
60
|
+
const labels = ((rows.results ?? []) as { label: string }[]).map((r) => r.label).filter(Boolean);
|
|
61
|
+
return {
|
|
62
|
+
name: "graph.cites",
|
|
63
|
+
query: `${doc}${edition ? ` (${edition})` : ""}`,
|
|
64
|
+
output: labels.length
|
|
65
|
+
? `The publication's bibliography cites:\n${labels.map((l) => `- ${l}`).join("\n")}`
|
|
66
|
+
: `No bibliography citations are indexed for "${doc}"${edition ? ` (${edition})` : ""}. State this plainly.`,
|
|
67
|
+
};
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const docsSection: ToolSpec = {
|
|
75
|
+
name: "docs.section",
|
|
76
|
+
description:
|
|
77
|
+
"Fetch one clause of a publication's rendered document — its text and its permanent deep-link URL — for agents that need the human-formatted passage with its anchor.",
|
|
78
|
+
params: [
|
|
79
|
+
{ key: "doc", required: true, description: "the publication identifier, e.g. R 60-1:2021" },
|
|
80
|
+
{ key: "clause", required: true, description: "the clause anchor, e.g. 5.1.1 or annex-b" },
|
|
81
|
+
],
|
|
82
|
+
audiences: ["mcp"],
|
|
83
|
+
handler: async (env, args) => {
|
|
84
|
+
const doc = String(args?.doc ?? "").trim();
|
|
85
|
+
const clause = String(args?.clause ?? "").trim();
|
|
86
|
+
if (doc.length < 3 || !clause) return null;
|
|
87
|
+
try {
|
|
88
|
+
const rows = await env.DB.prepare(
|
|
89
|
+
"SELECT docidentifier, clause_anchor, clause_title, text FROM chunks WHERE docidentifier LIKE ?1 AND REPLACE(clause_anchor, ' ', '') = ?2 LIMIT 1",
|
|
90
|
+
)
|
|
91
|
+
.bind(`%${doc.replace(/[:\s]+/g, "%")}%`, clause)
|
|
92
|
+
.all();
|
|
93
|
+
const r = (rows.results ?? [])[0] as { docidentifier: string; clause_anchor: string; clause_title: string; text: string } | undefined;
|
|
94
|
+
if (!r) {
|
|
95
|
+
return { name: "docs.section", query: `${doc} §${clause}`, output: `No clause "${clause}" is indexed for "${doc}". State this plainly.` };
|
|
96
|
+
}
|
|
97
|
+
const url = `https://www.ommisa.org/docs/${slug(r.docidentifier)}.html#${r.clause_anchor}`;
|
|
98
|
+
return {
|
|
99
|
+
name: "docs.section",
|
|
100
|
+
query: `${r.docidentifier} §${r.clause_anchor}`,
|
|
101
|
+
output: JSON.stringify({ docidentifier: r.docidentifier, clause: r.clause_anchor, title: r.clause_title, url, text: (r.text ?? "").slice(0, 4000) }),
|
|
102
|
+
};
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const glossaryLookup: ToolSpec = {
|
|
110
|
+
name: "glossary.lookup",
|
|
111
|
+
description:
|
|
112
|
+
"Look up a defined term in the terminology datasets — the concept's definition and its defining publication. Bind everyday words to the defined term before interpreting a question.",
|
|
113
|
+
params: [{ key: "term", required: true, description: "the term or everyday phrase to bind, e.g. creep or load cell" }],
|
|
114
|
+
audiences: ["mcp"],
|
|
115
|
+
handler: async (env, args) => {
|
|
116
|
+
const term = String(args?.term ?? "").trim();
|
|
117
|
+
if (term.length < 2) return null;
|
|
118
|
+
try {
|
|
119
|
+
const rows = await env.DB.prepare(
|
|
120
|
+
"SELECT clause_title AS term, chunk_text AS text, docidentifier FROM chunks WHERE corpus = 'glossary' AND (clause_title = ?1 OR clause_title LIKE ?2) LIMIT 3",
|
|
121
|
+
)
|
|
122
|
+
.bind(term, `%${term}%`)
|
|
123
|
+
.all();
|
|
124
|
+
const hits = ((rows.results ?? []) as { term: string; text: string; docidentifier: string }[]).filter((r) => r.term);
|
|
125
|
+
if (!hits.length) {
|
|
126
|
+
return { name: "glossary.lookup", query: term, output: `No defined term matches "${term}" in the terminology datasets. State this plainly.` };
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
name: "glossary.lookup",
|
|
130
|
+
query: term,
|
|
131
|
+
output: hits
|
|
132
|
+
.map((r) => `- ${r.term} (${r.docidentifier}): ${String(r.text ?? "").split(" — ").slice(1).join(" — ").slice(0, 400)}`)
|
|
133
|
+
.join("\n"),
|
|
134
|
+
};
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
};
|
|
@@ -43,7 +43,7 @@ export interface ToolSpec {
|
|
|
43
43
|
description: string;
|
|
44
44
|
params: { key: string; required: boolean; description: string }[];
|
|
45
45
|
audiences: ("agent" | "mcp")[];
|
|
46
|
-
handler: (
|
|
46
|
+
handler: (env: any, args: Record<string, unknown>) => Promise<ToolResult | null>;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const certificatesSearch: ToolSpec = {
|
|
@@ -52,11 +52,11 @@ const certificatesSearch: ToolSpec = {
|
|
|
52
52
|
"Search the certificate register (a snapshot) by holder name, model designation, or a printed certificate number. Returns the matching rows — number, holder, model, issue year, status, and document links where on file — or the exact no-match statement for the string asked.",
|
|
53
53
|
params: [{ key: "query", required: true, description: "the holder, model, or printed certificate number to look up" }],
|
|
54
54
|
audiences: ["agent", "mcp"],
|
|
55
|
-
handler: async (
|
|
55
|
+
handler: async (env, args) => {
|
|
56
56
|
const query = String(args?.query ?? "").trim().slice(0, 160);
|
|
57
57
|
if (!query) return null;
|
|
58
58
|
const { searchRegister, registerNote, certificateLinks } = await import("./certificates");
|
|
59
|
-
const reg = await searchRegister(
|
|
59
|
+
const reg = await searchRegister(env.DB, query, true); // the tool decided — no shaping gate
|
|
60
60
|
const output = reg?.rows?.length
|
|
61
61
|
? [registerNote(reg.rows), certificateLinks(reg.rows)].filter(Boolean).join("\n")
|
|
62
62
|
: `No certificate was found for "${query}" in the certificates database (the register snapshot). State this as the search's result, with the searched string visible.`;
|
|
@@ -64,7 +64,9 @@ const certificatesSearch: ToolSpec = {
|
|
|
64
64
|
},
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
import { unitsGet, graphCites, docsSection, glossaryLookup } from "./tools-content.ts";
|
|
68
|
+
|
|
69
|
+
export const TOOLS_REGISTRY: ToolSpec[] = [certificatesSearch, unitsGet, graphCites, docsSection, glossaryLookup];
|
|
68
70
|
|
|
69
71
|
/** The agent loop's protocol declaration, GENERATED from the registry —
|
|
70
72
|
* the model's tool vocabulary is always the registry's, never a
|
|
@@ -79,10 +81,10 @@ export const TOOL_DECLARATION = [
|
|
|
79
81
|
].join("\n");
|
|
80
82
|
|
|
81
83
|
/** Dispatch by LOOKUP — a tool name is never hard-coded at a call site. */
|
|
82
|
-
export async function runTool(
|
|
84
|
+
export async function runTool(env: any, call: ToolCall): Promise<ToolResult | null> {
|
|
83
85
|
const spec = TOOLS_REGISTRY.find((t) => t.name === call.name && t.audiences.includes("agent"));
|
|
84
86
|
if (!spec) return null;
|
|
85
|
-
return spec.handler(
|
|
87
|
+
return spec.handler(env, call.args ?? {});
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
/** The attributed injection: the answer model sees what the tool
|