@konneal/engine 0.2.85 → 0.2.87

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.
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  handleAsk
3
- } from "./chunk-R3KA6S3E.js";
3
+ } from "./chunk-PQCRL5TL.js";
4
+ import "./chunk-GCK6DW7E.js";
4
5
  import "./chunk-7HWJOFTQ.js";
5
6
  import "./chunk-SPKPH54J.js";
6
7
  import "./chunk-A3QHHUN5.js";
@@ -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.DB, call);
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);
@@ -2976,6 +2871,26 @@ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2976
2871
  }
2977
2872
  const tq2 = await chargeAnswerTokens(env, bucketId, stats, messageChars(messages), full.length);
2978
2873
  send({ type: "quota", quota: { ...quota, ...tq2 } });
2874
+ if (withImage && register?.rows?.length && full.includes(refusalAnswer())) {
2875
+ console.log("blind-answer(stream): regenerating once");
2876
+ mark("Re-answering from the register results");
2877
+ send({ type: "retry" });
2878
+ const regen = await generateStream(env, model, [
2879
+ ...messages.slice(0, -1),
2880
+ { role: "user", content: "The photograph was attached and the register note lists actual matching certificate rows. Present those rows \u2014 number, holder, model, status, and every document link \u2014 as the answer. Do not claim that nothing was provided." },
2881
+ messages[messages.length - 1]
2882
+ ], effort);
2883
+ if (regen) {
2884
+ full = "";
2885
+ try {
2886
+ for await (const tok of sseTokens(regen)) {
2887
+ full += tok;
2888
+ send({ type: "token", v: tok });
2889
+ }
2890
+ } catch {
2891
+ }
2892
+ }
2893
+ }
2979
2894
  const canonical0 = canonicalRefusal(full);
2980
2895
  const c2 = canonical0.includes(refusalAnswer()) ? { text: canonical0, blocks: [], dropped: [] } : await contractV2(env.DB, canonical0, usedHits);
2981
2896
  send({
@@ -3179,6 +3094,5 @@ export {
3179
3094
  bindModelNode,
3180
3095
  modelGroundingBlock,
3181
3096
  scoreJudge,
3182
- TOOLS_REGISTRY,
3183
3097
  handleAsk
3184
3098
  };
@@ -0,0 +1,14 @@
1
+ import {
2
+ TOOLS_REGISTRY,
3
+ TOOL_DECLARATION,
4
+ parseToolCall,
5
+ runTool,
6
+ toolNote
7
+ } from "./chunk-GCK6DW7E.js";
8
+ export {
9
+ TOOLS_REGISTRY,
10
+ TOOL_DECLARATION,
11
+ parseToolCall,
12
+ runTool,
13
+ toolNote
14
+ };
@@ -0,0 +1,5 @@
1
+ import type { ToolSpec } from "./tools.ts";
2
+ export declare const unitsGet: ToolSpec;
3
+ export declare const graphCites: ToolSpec;
4
+ export declare const docsSection: ToolSpec;
5
+ export declare const glossaryLookup: ToolSpec;
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: (db: any, args: Record<string, unknown>) => Promise<ToolResult | null>;
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(db: any, call: ToolCall): Promise<ToolResult | null>;
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-R3KA6S3E.js";
9
+ } from "../../chunk-PQCRL5TL.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 res = name === "ask" ? await (await import("../../ask-52JUHGUG.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-LVUQLA3C.js")).handleSearch(env, ctx, inner, tier, key);
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-IDMQUJBY.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.85",
30
+ "version": "0.2.87",
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.DB, call);
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);
@@ -1329,6 +1329,32 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1329
1329
  }
1330
1330
  const tq = await chargeAnswerTokens(env, bucketId, stats, messageChars(messages), full.length);
1331
1331
  send({ type: "quota", quota: { ...quota, ...tq } });
1332
+ // the blind-answer guard ON THE STREAM PATH (the JSON path has
1333
+ // had it since 0.2.80): an image rode the turn, the register
1334
+ // note carries rows, and the streamed text is the no-information
1335
+ // refusal — the multimodal call answered around the photograph.
1336
+ // One bounded regeneration: the retry sentinel tells the client
1337
+ // to clear the buffer, the agent step says why, and the fresh
1338
+ // tokens stream as the answer.
1339
+ if (withImage && register?.rows?.length && full.includes(refusalAnswer())) {
1340
+ console.log("blind-answer(stream): regenerating once");
1341
+ mark("Re-answering from the register results");
1342
+ send({ type: "retry" });
1343
+ const regen = await generateStream(env, model, [
1344
+ ...messages.slice(0, -1),
1345
+ { role: "user", content: "The photograph was attached and the register note lists actual matching certificate rows. Present those rows — number, holder, model, status, and every document link — as the answer. Do not claim that nothing was provided." },
1346
+ messages[messages.length - 1],
1347
+ ], effort);
1348
+ if (regen) {
1349
+ full = "";
1350
+ try {
1351
+ for await (const tok of sseTokens(regen)) {
1352
+ full += tok;
1353
+ send({ type: "token", v: tok });
1354
+ }
1355
+ } catch { /* deliver what we have */ }
1356
+ }
1357
+ }
1332
1358
  const canonical0 = canonicalRefusal(full);
1333
1359
  // answer contract v2: validate [[u:]] refs, resolve typed blocks
1334
1360
  const c2 = canonical0.includes(refusalAnswer())
@@ -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: (db: any, args: Record<string, unknown>) => Promise<ToolResult | null>;
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 (db, args) => {
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(db, query, true); // the tool decided — no shaping gate
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
- export const TOOLS_REGISTRY: ToolSpec[] = [certificatesSearch];
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(db: any, call: ToolCall): Promise<ToolResult | null> {
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(db, call.args ?? {});
87
+ return spec.handler(env, call.args ?? {});
86
88
  }
87
89
 
88
90
  /** The attributed injection: the answer model sees what the tool