@konneal/engine 0.2.84 → 0.2.85
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-GVU5PYC4.js → ask-52JUHGUG.js} +1 -1
- package/dist/{chunk-D24PQPDK.js → chunk-R3KA6S3E.js} +29 -12
- package/dist/tools.d.ts +19 -2
- package/dist/worker_public/src/index.js +13 -3
- package/package.json +1 -1
- package/workers/worker_public/src/ask.ts +1 -1
- package/workers/worker_public/src/mcp-proto.ts +14 -0
- package/workers/worker_public/src/tools.ts +52 -15
|
@@ -1875,7 +1875,7 @@ function exactCacheKey(indexVersion, gen, ns, queryHash) {
|
|
|
1875
1875
|
|
|
1876
1876
|
// workers/worker_public/src/tools.ts
|
|
1877
1877
|
function parseToolCall(text) {
|
|
1878
|
-
const m = /^TOOL\s+([a-z_]+)\s*(\{[^\n}]*\})?/m.exec(String(text ?? ""));
|
|
1878
|
+
const m = /^TOOL\s+([a-z_.]+)\s*(\{[^\n}]*\})?/m.exec(String(text ?? ""));
|
|
1879
1879
|
if (!m) return null;
|
|
1880
1880
|
let args = {};
|
|
1881
1881
|
if (m[2]) {
|
|
@@ -1884,22 +1884,38 @@ function parseToolCall(text) {
|
|
|
1884
1884
|
} catch {
|
|
1885
1885
|
return null;
|
|
1886
1886
|
}
|
|
1887
|
-
} else if (/^TOOL\s+[a-z_]+\s*\{/m.test(String(text ?? ""))) {
|
|
1887
|
+
} else if (/^TOOL\s+[a-z_.]+\s*\{/m.test(String(text ?? ""))) {
|
|
1888
1888
|
return null;
|
|
1889
1889
|
}
|
|
1890
1890
|
return { name: m[1].toLowerCase(), args };
|
|
1891
1891
|
}
|
|
1892
|
-
var
|
|
1893
|
-
|
|
1894
|
-
|
|
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");
|
|
1895
1915
|
async function runTool(db, call) {
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
const { searchRegister: searchRegister2, registerNote: registerNote2 } = await import("./certificates-CO7KX2S2.js");
|
|
1900
|
-
const reg = await searchRegister2(db, query, true);
|
|
1901
|
-
const output = reg?.rows?.length ? registerNote2(reg.rows) : `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.`;
|
|
1902
|
-
return { name: call.name, query, output };
|
|
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 ?? {});
|
|
1903
1919
|
}
|
|
1904
1920
|
function toolNote(r) {
|
|
1905
1921
|
return `The ${r.name} tool returned, for the query "${r.query}":
|
|
@@ -3163,5 +3179,6 @@ export {
|
|
|
3163
3179
|
bindModelNode,
|
|
3164
3180
|
modelGroundingBlock,
|
|
3165
3181
|
scoreJudge,
|
|
3182
|
+
TOOLS_REGISTRY,
|
|
3166
3183
|
handleAsk
|
|
3167
3184
|
};
|
package/dist/tools.d.ts
CHANGED
|
@@ -7,11 +7,28 @@ export interface ToolResult {
|
|
|
7
7
|
query: string;
|
|
8
8
|
output: string;
|
|
9
9
|
}
|
|
10
|
-
/** The strict protocol: a line `TOOL
|
|
10
|
+
/** The strict protocol: a line `TOOL certificates.search {"query": "…"}`.
|
|
11
11
|
* Plain regex parse — no JSON mode needed, tolerant of surrounding
|
|
12
12
|
* prose (the model may talk around the call). */
|
|
13
13
|
export declare function parseToolCall(text: string): ToolCall | null;
|
|
14
|
-
export
|
|
14
|
+
export interface ToolSpec {
|
|
15
|
+
/** canonical on every surface */
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
params: {
|
|
19
|
+
key: string;
|
|
20
|
+
required: boolean;
|
|
21
|
+
description: string;
|
|
22
|
+
}[];
|
|
23
|
+
audiences: ("agent" | "mcp")[];
|
|
24
|
+
handler: (db: any, args: Record<string, unknown>) => Promise<ToolResult | null>;
|
|
25
|
+
}
|
|
26
|
+
export declare const TOOLS_REGISTRY: ToolSpec[];
|
|
27
|
+
/** The agent loop's protocol declaration, GENERATED from the registry —
|
|
28
|
+
* the model's tool vocabulary is always the registry's, never a
|
|
29
|
+
* hand-maintained literal. */
|
|
30
|
+
export declare const TOOL_DECLARATION: string;
|
|
31
|
+
/** Dispatch by LOOKUP — a tool name is never hard-coded at a call site. */
|
|
15
32
|
export declare function runTool(db: any, call: ToolCall): Promise<ToolResult | null>;
|
|
16
33
|
/** The attributed injection: the answer model sees what the tool
|
|
17
34
|
* returned, for the string it asked — never a standing claim. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
TOOLS_REGISTRY,
|
|
2
3
|
bindModelNode,
|
|
3
4
|
checkQuoteAnchors,
|
|
4
5
|
handleAsk,
|
|
@@ -6,7 +7,7 @@ import {
|
|
|
6
7
|
modelGroundingBlock,
|
|
7
8
|
scoreFaithfulness,
|
|
8
9
|
scoreJudge
|
|
9
|
-
} from "../../chunk-
|
|
10
|
+
} from "../../chunk-R3KA6S3E.js";
|
|
10
11
|
import {
|
|
11
12
|
handleSearch
|
|
12
13
|
} from "../../chunk-KO6DRI7C.js";
|
|
@@ -922,7 +923,16 @@ var TOOLS = [
|
|
|
922
923
|
},
|
|
923
924
|
required: ["query"]
|
|
924
925
|
}
|
|
925
|
-
}
|
|
926
|
+
},
|
|
927
|
+
...TOOLS_REGISTRY.filter((t) => t.audiences.includes("mcp")).map((t) => ({
|
|
928
|
+
name: t.name,
|
|
929
|
+
description: t.description,
|
|
930
|
+
inputSchema: {
|
|
931
|
+
type: "object",
|
|
932
|
+
properties: Object.fromEntries(t.params.map((p) => [p.key, { type: "string", description: p.description }])),
|
|
933
|
+
required: t.params.filter((p) => p.required).map((p) => p.key)
|
|
934
|
+
}
|
|
935
|
+
}))
|
|
926
936
|
];
|
|
927
937
|
function dispatch(method, params, callTool) {
|
|
928
938
|
switch (method) {
|
|
@@ -959,7 +969,7 @@ async function handleMcp(env, ctx, req, tier, key) {
|
|
|
959
969
|
// stream:false forces the JSON lane (anon defaults to SSE)
|
|
960
970
|
body: JSON.stringify({ ...args, stream: false })
|
|
961
971
|
});
|
|
962
|
-
const res = name === "ask" ? await (await import("../../ask-
|
|
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);
|
|
963
973
|
return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
|
|
964
974
|
});
|
|
965
975
|
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.85",
|
|
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",
|
|
@@ -1227,7 +1227,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1227
1227
|
await attachFigureImages(env, messages, usedHits, q.query);
|
|
1228
1228
|
// the agent loop (the owner's 2026-09-28 direction): ONE bounded tool
|
|
1229
1229
|
// round, armed only where a tool can help. The model may call
|
|
1230
|
-
//
|
|
1230
|
+
// the registry tools through the strict line protocol; the worker
|
|
1231
1231
|
// executes it deterministically and the ATTRIBUTED result joins the
|
|
1232
1232
|
// messages — what the tool returned, for the string it asked. The
|
|
1233
1233
|
// invocations record into pendingTools; the stream emits them as its
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The MCP protocol surface (JSON-RPC 2.0, streamable HTTP 2025-06-18),
|
|
2
2
|
// dependency-free so plain node can load it for unit tests — the
|
|
3
3
|
// route adapter (mcp.ts) owns the handler wiring.
|
|
4
|
+
import { TOOLS_REGISTRY } from "./tools.ts";
|
|
4
5
|
export const PROTOCOL_VERSION = "2025-06-18";
|
|
5
6
|
|
|
6
7
|
export interface McpTool {
|
|
@@ -9,6 +10,8 @@ export interface McpTool {
|
|
|
9
10
|
inputSchema: { type: "object"; properties: Record<string, unknown>; required: string[] };
|
|
10
11
|
}
|
|
11
12
|
|
|
13
|
+
// the registry's MCP-audience tools are GENERATED here (OCP: adding a
|
|
14
|
+
// tool registers it; this list is never hand-extended for them)
|
|
12
15
|
export const TOOLS: McpTool[] = [
|
|
13
16
|
{
|
|
14
17
|
name: "ask",
|
|
@@ -34,6 +37,17 @@ export const TOOLS: McpTool[] = [
|
|
|
34
37
|
required: ["query"],
|
|
35
38
|
},
|
|
36
39
|
},
|
|
40
|
+
...TOOLS_REGISTRY
|
|
41
|
+
.filter((t) => t.audiences.includes("mcp"))
|
|
42
|
+
.map((t): McpTool => ({
|
|
43
|
+
name: t.name,
|
|
44
|
+
description: t.description,
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: Object.fromEntries(t.params.map((p) => [p.key, { type: "string", description: p.description }])),
|
|
48
|
+
required: t.params.filter((p) => p.required).map((p) => p.key),
|
|
49
|
+
},
|
|
50
|
+
})),
|
|
37
51
|
];
|
|
38
52
|
|
|
39
53
|
export type McpResult =
|
|
@@ -17,35 +17,72 @@ export interface ToolResult {
|
|
|
17
17
|
output: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/** The strict protocol: a line `TOOL
|
|
20
|
+
/** The strict protocol: a line `TOOL certificates.search {"query": "…"}`.
|
|
21
21
|
* Plain regex parse — no JSON mode needed, tolerant of surrounding
|
|
22
22
|
* prose (the model may talk around the call). */
|
|
23
23
|
export function parseToolCall(text: string): ToolCall | null {
|
|
24
|
-
const m = /^TOOL\s+([a-z_]+)\s*(\{[^\n}]*\})?/m.exec(String(text ?? "")); // line-anchored, case-sensitive: prose never trips it
|
|
24
|
+
const m = /^TOOL\s+([a-z_.]+)\s*(\{[^\n}]*\})?/m.exec(String(text ?? "")); // line-anchored, case-sensitive: prose never trips it
|
|
25
25
|
if (!m) return null;
|
|
26
26
|
let args: Record<string, unknown> = {};
|
|
27
27
|
if (m[2]) {
|
|
28
28
|
try { args = JSON.parse(m[2]); } catch { return null; }
|
|
29
|
-
} else if (/^TOOL\s+[a-z_]+\s*\{/m.test(String(text ?? ""))) {
|
|
29
|
+
} else if (/^TOOL\s+[a-z_.]+\s*\{/m.test(String(text ?? ""))) {
|
|
30
30
|
return null; // a malformed call (opened, never closed) is not a call
|
|
31
31
|
}
|
|
32
32
|
return { name: m[1].toLowerCase(), args };
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
// ── the tool registry (OCP, the owner's 2026-10-04 direction) ────────
|
|
36
|
+
// ONE declared table is the single source of the tool surface: the agent
|
|
37
|
+
// loop's declaration is GENERATED from it, runTool dispatches by lookup,
|
|
38
|
+
// and the MCP server's tools/list is generated from the same entries.
|
|
39
|
+
// Adding a tool = adding one registration; no call site is edited.
|
|
40
|
+
export interface ToolSpec {
|
|
41
|
+
/** canonical on every surface */
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
params: { key: string; required: boolean; description: string }[];
|
|
45
|
+
audiences: ("agent" | "mcp")[];
|
|
46
|
+
handler: (db: any, args: Record<string, unknown>) => Promise<ToolResult | null>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const certificatesSearch: ToolSpec = {
|
|
50
|
+
name: "certificates.search",
|
|
51
|
+
description:
|
|
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
|
+
params: [{ key: "query", required: true, description: "the holder, model, or printed certificate number to look up" }],
|
|
54
|
+
audiences: ["agent", "mcp"],
|
|
55
|
+
handler: async (db, args) => {
|
|
56
|
+
const query = String(args?.query ?? "").trim().slice(0, 160);
|
|
57
|
+
if (!query) return null;
|
|
58
|
+
const { searchRegister, registerNote, certificateLinks } = await import("./certificates");
|
|
59
|
+
const reg = await searchRegister(db, query, true); // the tool decided — no shaping gate
|
|
60
|
+
const output = reg?.rows?.length
|
|
61
|
+
? [registerNote(reg.rows), certificateLinks(reg.rows)].filter(Boolean).join("\n")
|
|
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.`;
|
|
63
|
+
return { name: "certificates.search", query, output };
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const TOOLS_REGISTRY: ToolSpec[] = [certificatesSearch];
|
|
68
|
+
|
|
69
|
+
/** The agent loop's protocol declaration, GENERATED from the registry —
|
|
70
|
+
* the model's tool vocabulary is always the registry's, never a
|
|
71
|
+
* hand-maintained literal. */
|
|
72
|
+
export const TOOL_DECLARATION = [
|
|
73
|
+
"You may use one tool before answering, by writing a single line:",
|
|
74
|
+
...TOOLS_REGISTRY.filter((t) => t.audiences.includes("agent")).map((t) => {
|
|
75
|
+
const shape = `{${t.params.map((p) => `"${p.key}": "<${p.description}>"`).join(", ")}}`;
|
|
76
|
+
return `TOOL ${t.name} ${shape}`;
|
|
77
|
+
}),
|
|
78
|
+
"The worker runs it and returns the result attributed — 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.",
|
|
79
|
+
].join("\n");
|
|
38
80
|
|
|
81
|
+
/** Dispatch by LOOKUP — a tool name is never hard-coded at a call site. */
|
|
39
82
|
export async function runTool(db: any, call: ToolCall): Promise<ToolResult | null> {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const { searchRegister, registerNote } = await import("./certificates");
|
|
44
|
-
const reg = await searchRegister(db, query, true); // the tool decided — no shaping gate
|
|
45
|
-
const output = reg?.rows?.length
|
|
46
|
-
? registerNote(reg.rows)
|
|
47
|
-
: `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.`;
|
|
48
|
-
return { name: call.name, query, output };
|
|
83
|
+
const spec = TOOLS_REGISTRY.find((t) => t.name === call.name && t.audiences.includes("agent"));
|
|
84
|
+
if (!spec) return null;
|
|
85
|
+
return spec.handler(db, call.args ?? {});
|
|
49
86
|
}
|
|
50
87
|
|
|
51
88
|
/** The attributed injection: the answer model sees what the tool
|