@kognai/tool-router 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +47 -2
- package/dist/adapters/awesome-list.d.ts +10 -0
- package/dist/adapters/awesome-list.js +68 -0
- package/dist/adapters/mcp-registry.d.ts +12 -0
- package/dist/adapters/mcp-registry.js +51 -0
- package/dist/adapters/mcp-tools.d.ts +49 -0
- package/dist/adapters/mcp-tools.js +60 -0
- package/dist/adapters/openapi.d.ts +25 -0
- package/dist/adapters/openapi.js +107 -0
- package/dist/adapters/pay-skills.d.ts +13 -0
- package/dist/adapters/pay-skills.js +119 -0
- package/dist/adapters/raw.d.ts +82 -0
- package/dist/adapters/raw.js +5 -0
- package/dist/adapters/x402-discovery.d.ts +6 -0
- package/dist/adapters/x402-discovery.js +100 -0
- package/dist/catalog/file-store.d.ts +27 -0
- package/dist/catalog/file-store.js +102 -0
- package/dist/catalog/ingest.d.ts +53 -0
- package/dist/catalog/ingest.js +276 -0
- package/dist/catalog/lifecycle.d.ts +24 -0
- package/dist/catalog/lifecycle.js +51 -0
- package/dist/catalog/memory-store.d.ts +49 -0
- package/dist/catalog/memory-store.js +92 -0
- package/dist/catalog/risk.d.ts +22 -0
- package/dist/catalog/risk.js +73 -0
- package/dist/catalog/sanitise.d.ts +17 -0
- package/dist/catalog/sanitise.js +109 -0
- package/dist/classify/classifier.d.ts +12 -0
- package/dist/classify/classifier.js +52 -0
- package/dist/classify/decompose.d.ts +10 -0
- package/dist/classify/decompose.js +107 -0
- package/dist/classify/rules.d.ts +46 -0
- package/dist/classify/rules.js +249 -0
- package/dist/classify/tagger.d.ts +5 -0
- package/dist/classify/tagger.js +29 -0
- package/dist/classify/taxonomy.d.ts +31 -0
- package/dist/classify/taxonomy.js +221 -0
- package/dist/classify/text.d.ts +9 -0
- package/dist/classify/text.js +38 -0
- package/dist/eligibility/engine.d.ts +11 -0
- package/dist/eligibility/engine.js +19 -0
- package/dist/eligibility/rules.d.ts +33 -0
- package/dist/eligibility/rules.js +121 -0
- package/dist/flags.d.ts +33 -0
- package/dist/flags.js +34 -0
- package/dist/hash.d.ts +8 -0
- package/dist/hash.js +30 -0
- package/dist/ids.d.ts +17 -0
- package/dist/ids.js +54 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +95 -0
- package/dist/money.d.ts +10 -0
- package/dist/money.js +41 -0
- package/dist/rank/confidence.d.ts +15 -0
- package/dist/rank/confidence.js +48 -0
- package/dist/rank/ranker.d.ts +34 -0
- package/dist/rank/ranker.js +81 -0
- package/dist/rank/weights.d.ts +40 -0
- package/dist/rank/weights.js +46 -0
- package/dist/retrieve/bm25.d.ts +21 -0
- package/dist/retrieve/bm25.js +59 -0
- package/dist/retrieve/capability.d.ts +1 -0
- package/dist/retrieve/capability.js +13 -0
- package/dist/retrieve/hash-embed.d.ts +4 -0
- package/dist/retrieve/hash-embed.js +41 -0
- package/dist/retrieve/hybrid.d.ts +74 -0
- package/dist/retrieve/hybrid.js +125 -0
- package/dist/retrieve/schema-fit.d.ts +9 -0
- package/dist/retrieve/schema-fit.js +38 -0
- package/dist/retrieve/tokenise.d.ts +2 -0
- package/dist/retrieve/tokenise.js +51 -0
- package/dist/retrieve/vector.d.ts +18 -0
- package/dist/retrieve/vector.js +47 -0
- package/dist/router.d.ts +37 -0
- package/dist/router.js +114 -0
- package/dist/schema/tool.schema.json +117 -0
- package/dist/schema/validate.d.ts +9 -0
- package/dist/schema/validate.js +92 -0
- package/dist/shortlist/generate.d.ts +21 -0
- package/dist/shortlist/generate.js +75 -0
- package/dist/types/ports.d.ts +71 -0
- package/dist/types/ports.js +2 -0
- package/dist/types/route.d.ts +121 -0
- package/dist/types/route.js +5 -0
- package/dist/types/tool.d.ts +123 -0
- package/dist/types/tool.js +17 -0
- package/package.json +39 -4
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://kognai.ai/schemas/ktr/tool-record-v1.json",
|
|
4
|
+
"title": "KTR ToolRecord v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version", "id", "version", "name", "description_safe", "description_raw_sha256", "provider", "origin",
|
|
9
|
+
"category", "tags", "operations", "auth", "privacy_class", "business_tier", "regions", "networks", "pricing",
|
|
10
|
+
"success", "latency", "rate_limits", "health", "dependencies", "trust_state", "skill_ids", "last_validated",
|
|
11
|
+
"schema_hash", "implementation_hash", "lifecycle", "aliases", "same_as", "provenance", "ingested_at"
|
|
12
|
+
],
|
|
13
|
+
"properties": {
|
|
14
|
+
"schema_version": { "enum": [1] },
|
|
15
|
+
"id": { "type": "string", "maxLength": 160, "pattern": "^tool:(kognai|mcp|openapi|x402|community|composio):[a-z0-9._-]+(/[a-z0-9._-]+)+$" },
|
|
16
|
+
"version": { "type": "string", "minLength": 1, "maxLength": 80 },
|
|
17
|
+
"name": { "type": "string", "minLength": 1, "maxLength": 120 },
|
|
18
|
+
"description_safe": { "type": "string", "maxLength": 512 },
|
|
19
|
+
"description_raw_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
20
|
+
"provider": {
|
|
21
|
+
"type": "object", "additionalProperties": false, "required": ["name", "kognai_owned"],
|
|
22
|
+
"properties": { "name": { "type": "string", "maxLength": 120 }, "url": { "type": "string", "maxLength": 300 }, "kognai_owned": { "type": "boolean" } }
|
|
23
|
+
},
|
|
24
|
+
"origin": { "enum": ["kognai", "mcp", "openapi", "x402", "community", "composio"] },
|
|
25
|
+
"category": { "type": "string", "maxLength": 60 },
|
|
26
|
+
"tags": { "type": "array", "maxItems": 40, "items": { "type": "string", "maxLength": 60 } },
|
|
27
|
+
"operations": {
|
|
28
|
+
"type": "array", "maxItems": 200,
|
|
29
|
+
"items": {
|
|
30
|
+
"type": "object", "additionalProperties": false,
|
|
31
|
+
"required": ["op", "summary", "capabilities", "input_schema", "output_schema", "side_effects", "risk_classes", "scopes", "idempotent", "url_from_user"],
|
|
32
|
+
"properties": {
|
|
33
|
+
"op": { "type": "string", "pattern": "^[a-z0-9_.-]{1,64}$" },
|
|
34
|
+
"summary": { "type": "string", "maxLength": 200 },
|
|
35
|
+
"capabilities": { "type": "array", "items": { "type": "string", "pattern": "^[a-z0-9_]+(\\.[a-z0-9_]+)+$" } },
|
|
36
|
+
"input_schema": { "type": "object" },
|
|
37
|
+
"output_schema": { "type": ["object", "null"] },
|
|
38
|
+
"side_effects": { "type": "array", "items": { "enum": ["none", "read", "write", "publish", "message", "payment", "delete", "infra"] } },
|
|
39
|
+
"risk_classes": { "type": "array", "items": { "enum": ["read_only", "network", "data_modification", "publish_or_message", "payment", "destructive", "sensitive_data", "privileged_infra"] } },
|
|
40
|
+
"scopes": { "type": "array", "items": { "type": "string", "maxLength": 200 } },
|
|
41
|
+
"idempotent": { "type": ["boolean", "null"] },
|
|
42
|
+
"url_from_user": { "type": "boolean" }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"auth": {
|
|
47
|
+
"type": "object", "additionalProperties": false, "required": ["kind"],
|
|
48
|
+
"properties": { "kind": { "enum": ["none", "oauth", "api_key", "x402", "internal"] }, "credential_ref": { "type": "string" }, "provider": { "type": "string" } }
|
|
49
|
+
},
|
|
50
|
+
"privacy_class": { "enum": ["public", "internal", "private", "sensitive"] },
|
|
51
|
+
"business_tier": { "enum": ["free", "citizen_plus", "citizen_pro", "citizen_pro_max", "enterprise", null] },
|
|
52
|
+
"regions": { "type": "array", "items": { "type": "string", "maxLength": 3 } },
|
|
53
|
+
"networks": { "type": "array", "items": { "enum": ["base", "base-sepolia", "solana", "solana-devnet", "offchain"] } },
|
|
54
|
+
"pricing": {
|
|
55
|
+
"type": "object", "additionalProperties": false, "required": ["model", "source"],
|
|
56
|
+
"properties": {
|
|
57
|
+
"model": { "enum": ["free", "per_call", "per_unit", "unknown"] },
|
|
58
|
+
"price": {
|
|
59
|
+
"type": "object", "additionalProperties": false, "required": ["amount_micro", "currency"],
|
|
60
|
+
"properties": {
|
|
61
|
+
"amount_micro": { "type": "string", "pattern": "^[0-9]{1,30}$" },
|
|
62
|
+
"currency": { "enum": ["USDC", "KGC", "USD"] },
|
|
63
|
+
"network": { "enum": ["base", "base-sepolia", "solana", "solana-devnet", "offchain"] }
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"unit": { "type": "string", "maxLength": 40 },
|
|
67
|
+
"source": { "enum": ["measured", "provider", "cfo_price_book", "unknown"] },
|
|
68
|
+
"observed_at": { "type": "string" }
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"success": {
|
|
72
|
+
"type": "object", "additionalProperties": false,
|
|
73
|
+
"required": ["exec_ok", "exec_n", "verified_ok", "verified_n", "distinct_principals", "window_days"],
|
|
74
|
+
"properties": {
|
|
75
|
+
"exec_ok": { "type": "integer" }, "exec_n": { "type": "integer" }, "verified_ok": { "type": "integer" },
|
|
76
|
+
"verified_n": { "type": "integer" }, "distinct_principals": { "type": "integer" }, "window_days": { "type": "integer" }
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"latency": {
|
|
80
|
+
"type": "object", "additionalProperties": false, "required": ["p50_ms", "p95_ms", "n"],
|
|
81
|
+
"properties": { "p50_ms": { "type": ["number", "null"] }, "p95_ms": { "type": ["number", "null"] }, "n": { "type": "integer" } }
|
|
82
|
+
},
|
|
83
|
+
"rate_limits": { "type": ["object", "null"] },
|
|
84
|
+
"health": {
|
|
85
|
+
"type": "object", "additionalProperties": false, "required": ["state", "checked_at", "source"],
|
|
86
|
+
"properties": {
|
|
87
|
+
"state": { "enum": ["up", "degraded", "down", "unknown"] },
|
|
88
|
+
"checked_at": { "type": ["string", "null"] },
|
|
89
|
+
"source": { "enum": ["probe", "experience", "none"] }
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"dependencies": { "type": "array", "items": { "type": "string" } },
|
|
93
|
+
"trust_state": { "enum": ["quarantined", "reviewed", "verified", "deprecated", "revoked"] },
|
|
94
|
+
"skill_ids": { "type": "array", "items": { "type": "string" } },
|
|
95
|
+
"last_validated": { "type": ["string", "null"] },
|
|
96
|
+
"schema_hash": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
97
|
+
"implementation_hash": { "type": ["string", "null"] },
|
|
98
|
+
"lifecycle": { "enum": ["draft", "active", "deprecated", "retired"] },
|
|
99
|
+
"aliases": { "type": "array", "items": { "type": "string", "maxLength": 160 } },
|
|
100
|
+
"same_as": { "type": "array", "items": { "type": "string" } },
|
|
101
|
+
"product_id": { "type": "string" },
|
|
102
|
+
"aiax_ready": { "type": "boolean" },
|
|
103
|
+
"provenance": {
|
|
104
|
+
"type": "array", "minItems": 1,
|
|
105
|
+
"items": {
|
|
106
|
+
"type": "object", "additionalProperties": false,
|
|
107
|
+
"required": ["source", "source_ref", "retrieved_at", "licence", "adapter", "adapter_version", "snapshot_sha256"],
|
|
108
|
+
"properties": {
|
|
109
|
+
"source": { "type": "string" }, "source_ref": { "type": "string", "maxLength": 400 }, "retrieved_at": { "type": "string" },
|
|
110
|
+
"licence": { "type": ["string", "null"] }, "adapter": { "type": "string" }, "adapter_version": { "type": "string" },
|
|
111
|
+
"snapshot_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"ingested_at": { "type": "string" }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ValidateResult {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
errors: string[];
|
|
4
|
+
ignored: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function validate(schema: Record<string, any>, value: unknown, max?: number): ValidateResult;
|
|
7
|
+
export declare const TOOL_SCHEMA: Record<string, any>;
|
|
8
|
+
/** Validates a ToolRecord against the KTR schema. */
|
|
9
|
+
export declare const validateToolRecord: (rec: unknown) => ValidateResult;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateToolRecord = exports.TOOL_SCHEMA = void 0;
|
|
7
|
+
exports.validate = validate;
|
|
8
|
+
/**
|
|
9
|
+
* A minimal JSON Schema validator for the subset KTR emits (plan.md S1): type, required, enum, const, pattern,
|
|
10
|
+
* minLength, maxLength, minItems, maxItems, items, properties, additionalProperties. Any other keyword is ignored and
|
|
11
|
+
* reported in `ignored`, so a schema that relies on it is visible instead of silently passing. External tool schemas
|
|
12
|
+
* are only stored and hashed, never validated against arguments in milestone 1.
|
|
13
|
+
*/
|
|
14
|
+
const tool_schema_json_1 = __importDefault(require("./tool.schema.json"));
|
|
15
|
+
const SUPPORTED = new Set([
|
|
16
|
+
'type', 'required', 'enum', 'const', 'pattern', 'minLength', 'maxLength', 'minItems', 'maxItems', 'items', 'properties',
|
|
17
|
+
'additionalProperties', '$schema', '$id', 'title', 'description', 'default', 'examples',
|
|
18
|
+
]);
|
|
19
|
+
const typeOf = (v) => {
|
|
20
|
+
if (v === null)
|
|
21
|
+
return 'null';
|
|
22
|
+
if (Array.isArray(v))
|
|
23
|
+
return 'array';
|
|
24
|
+
if (typeof v === 'number')
|
|
25
|
+
return Number.isInteger(v) ? 'integer' : 'number';
|
|
26
|
+
return typeof v;
|
|
27
|
+
};
|
|
28
|
+
const typeMatches = (want, v) => {
|
|
29
|
+
const t = typeOf(v);
|
|
30
|
+
return want === t || (want === 'number' && t === 'integer');
|
|
31
|
+
};
|
|
32
|
+
const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
33
|
+
function validate(schema, value, max = 50) {
|
|
34
|
+
const errors = [];
|
|
35
|
+
const ignored = new Set();
|
|
36
|
+
const walk = (s, v, path) => {
|
|
37
|
+
if (errors.length >= max || !s || typeof s !== 'object')
|
|
38
|
+
return;
|
|
39
|
+
for (const k of Object.keys(s))
|
|
40
|
+
if (!SUPPORTED.has(k))
|
|
41
|
+
ignored.add(k);
|
|
42
|
+
if (s.type !== undefined) {
|
|
43
|
+
const types = Array.isArray(s.type) ? s.type : [s.type];
|
|
44
|
+
if (!types.some((t) => typeMatches(t, v))) {
|
|
45
|
+
errors.push(`${path || '/'}: expected ${types.join(' or ')}, got ${typeOf(v)}`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (s.enum !== undefined && !s.enum.some((e) => eq(e, v)))
|
|
50
|
+
errors.push(`${path || '/'}: not one of ${JSON.stringify(s.enum)}`);
|
|
51
|
+
if (s.const !== undefined && !eq(s.const, v))
|
|
52
|
+
errors.push(`${path || '/'}: must be ${JSON.stringify(s.const)}`);
|
|
53
|
+
if (typeof v === 'string') {
|
|
54
|
+
if (typeof s.maxLength === 'number' && [...v].length > s.maxLength)
|
|
55
|
+
errors.push(`${path}: longer than ${s.maxLength}`);
|
|
56
|
+
if (typeof s.minLength === 'number' && [...v].length < s.minLength)
|
|
57
|
+
errors.push(`${path}: shorter than ${s.minLength}`);
|
|
58
|
+
if (typeof s.pattern === 'string' && !new RegExp(s.pattern, 'u').test(v))
|
|
59
|
+
errors.push(`${path}: does not match ${s.pattern}`);
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(v)) {
|
|
62
|
+
if (typeof s.maxItems === 'number' && v.length > s.maxItems)
|
|
63
|
+
errors.push(`${path}: more than ${s.maxItems} items`);
|
|
64
|
+
if (typeof s.minItems === 'number' && v.length < s.minItems)
|
|
65
|
+
errors.push(`${path}: fewer than ${s.minItems} items`);
|
|
66
|
+
if (s.items && typeof s.items === 'object')
|
|
67
|
+
v.forEach((x, i) => walk(s.items, x, `${path}/${i}`));
|
|
68
|
+
}
|
|
69
|
+
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
70
|
+
const o = v;
|
|
71
|
+
if (Array.isArray(s.required))
|
|
72
|
+
for (const r of s.required)
|
|
73
|
+
if (!(r in o))
|
|
74
|
+
errors.push(`${path}/${r}: required`);
|
|
75
|
+
const props = s.properties || {};
|
|
76
|
+
for (const [k, x] of Object.entries(o)) {
|
|
77
|
+
if (props[k])
|
|
78
|
+
walk(props[k], x, `${path}/${k}`);
|
|
79
|
+
else if (s.additionalProperties === false)
|
|
80
|
+
errors.push(`${path}/${k}: not allowed`);
|
|
81
|
+
else if (s.additionalProperties && typeof s.additionalProperties === 'object')
|
|
82
|
+
walk(s.additionalProperties, x, `${path}/${k}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
walk(schema, value, '');
|
|
87
|
+
return { ok: errors.length === 0, errors, ignored: [...ignored].sort() };
|
|
88
|
+
}
|
|
89
|
+
exports.TOOL_SCHEMA = tool_schema_json_1.default;
|
|
90
|
+
/** Validates a ToolRecord against the KTR schema. */
|
|
91
|
+
const validateToolRecord = (rec) => validate(exports.TOOL_SCHEMA, rec);
|
|
92
|
+
exports.validateToolRecord = validateToolRecord;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shortlist (plan.md S3): at most min(10, max_candidates) per node, eligible only, above the relevance floor, with
|
|
3
|
+
* reasons, fallbacks and auth needs. Kognai first (ADR-008): with kognai_first 'hard', when a Kognai product covers the
|
|
4
|
+
* step, outside tools are returned only as fallbacks of the first Kognai candidate; with 'prior' Kognai tools get a
|
|
5
|
+
* small bonus; with 'off' merit alone. Linked duplicates (same_as) collapse into fallbacks. Nothing in a candidate is
|
|
6
|
+
* copied from provider text except its name.
|
|
7
|
+
*/
|
|
8
|
+
import type { Candidate, CallerContext, PlanNode } from '../types/route';
|
|
9
|
+
import type { Ranked } from '../rank/ranker';
|
|
10
|
+
export declare const MAX_CANDIDATES = 10;
|
|
11
|
+
export declare const PRIOR_BONUS = 0.05;
|
|
12
|
+
export declare const CONNECTED_BONUS = 0.02;
|
|
13
|
+
export interface ShortlistOpts {
|
|
14
|
+
max?: number;
|
|
15
|
+
floor?: number;
|
|
16
|
+
mode: 'discover' | 'execute_plan';
|
|
17
|
+
}
|
|
18
|
+
export declare function generate(ranked: Ranked[], node: PlanNode, ctx: CallerContext, o: ShortlistOpts): {
|
|
19
|
+
candidates: Candidate[];
|
|
20
|
+
kognaiFirst: boolean;
|
|
21
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CONNECTED_BONUS = exports.PRIOR_BONUS = exports.MAX_CANDIDATES = void 0;
|
|
4
|
+
exports.generate = generate;
|
|
5
|
+
const rules_1 = require("../eligibility/rules");
|
|
6
|
+
const weights_1 = require("../rank/weights");
|
|
7
|
+
exports.MAX_CANDIDATES = 10;
|
|
8
|
+
exports.PRIOR_BONUS = 0.05;
|
|
9
|
+
exports.CONNECTED_BONUS = 0.02;
|
|
10
|
+
const r4 = (x) => Math.round(x * 10000) / 10000;
|
|
11
|
+
function generate(ranked, node, ctx, o) {
|
|
12
|
+
const max = Math.max(0, Math.min(exports.MAX_CANDIDATES, o.max ?? exports.MAX_CANDIDATES));
|
|
13
|
+
let list = ranked.filter((x) => x.fit >= (o.floor ?? weights_1.RELEVANCE_FLOOR));
|
|
14
|
+
// an account the owner already connected beats the same tool on an account still to connect
|
|
15
|
+
list = list.map((x) => (x.r.item.rec.auth.kind === 'oauth' && (0, rules_1.authNeeded)(x.r.item.rec, x.r.item.op, ctx) === null ? { ...x, score: x.score + exports.CONNECTED_BONUS, reasons: [...x.reasons, "the owner's account is already connected"] } : x))
|
|
16
|
+
.sort((a, b) => b.score - a.score || (a.r.item.key < b.r.item.key ? -1 : 1));
|
|
17
|
+
let outsideFallbacks = [];
|
|
18
|
+
let kognaiFirst = false;
|
|
19
|
+
if (ctx.kognai_first === 'prior') {
|
|
20
|
+
list = list.map((x) => ((0, rules_1.isKognaiProduct)(x.r.item.rec) ? { ...x, score: x.score + exports.PRIOR_BONUS } : x)).sort((a, b) => b.score - a.score || (a.r.item.key < b.r.item.key ? -1 : 1));
|
|
21
|
+
}
|
|
22
|
+
else if (ctx.kognai_first === 'hard') {
|
|
23
|
+
const covers = list.some((x) => (0, rules_1.isKognaiProduct)(x.r.item.rec) && x.r.signals.capability === 1);
|
|
24
|
+
if (covers) {
|
|
25
|
+
kognaiFirst = true;
|
|
26
|
+
outsideFallbacks = list.filter((x) => !(0, rules_1.isKognaiProduct)(x.r.item.rec)).slice(0, max).map((x) => x.r.item.key);
|
|
27
|
+
list = list.filter((x) => (0, rules_1.isKognaiProduct)(x.r.item.rec));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// same_as: keep the first (best) of a linked group, the others become its fallbacks
|
|
31
|
+
const kept = [];
|
|
32
|
+
const extra = new Map();
|
|
33
|
+
for (const x of list) {
|
|
34
|
+
const rec = x.r.item.rec;
|
|
35
|
+
const head = kept.find((k) => k.r.item.rec.same_as.includes(rec.id) || rec.same_as.includes(k.r.item.rec.id));
|
|
36
|
+
if (head) {
|
|
37
|
+
(extra.get(head.r.item.key) || extra.set(head.r.item.key, []).get(head.r.item.key)).push(x.r.item.key);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
kept.push(x);
|
|
41
|
+
if (kept.length >= max)
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
const candidates = kept.map((x, i) => {
|
|
45
|
+
const { rec, op } = x.r.item;
|
|
46
|
+
const reasons = [...x.reasons];
|
|
47
|
+
if (kognaiFirst && i === 0)
|
|
48
|
+
reasons.unshift('Kognai product first (ADR-008): outside tools are fallbacks');
|
|
49
|
+
const auth = (0, rules_1.authNeeded)(rec, op, ctx);
|
|
50
|
+
if (auth?.kind === 'oauth')
|
|
51
|
+
reasons.push(`needs the owner's ${auth.provider} connection`);
|
|
52
|
+
if (auth?.kind === 'approval')
|
|
53
|
+
reasons.push('needs an approval before it runs');
|
|
54
|
+
return {
|
|
55
|
+
tool_id: x.r.item.key,
|
|
56
|
+
version: rec.version,
|
|
57
|
+
operation: op.op,
|
|
58
|
+
name: rec.name,
|
|
59
|
+
input_schema: op.input_schema,
|
|
60
|
+
output_schema: op.output_schema,
|
|
61
|
+
score: r4(x.score),
|
|
62
|
+
score_parts: Object.fromEntries(Object.entries(x.parts).map(([k, v]) => [k, r4(v)])),
|
|
63
|
+
reasons,
|
|
64
|
+
cost: rec.pricing,
|
|
65
|
+
skills: x.skills.map((s) => ({ skill_id: s.skill_id, version: s.version, uplift: r4(s.uplift) })),
|
|
66
|
+
auth_needed: auth,
|
|
67
|
+
confidence: r4(x.confidence),
|
|
68
|
+
fallbacks: [...(extra.get(x.r.item.key) || []), ...(i === 0 ? outsideFallbacks : [])],
|
|
69
|
+
evidence: { verified_n: x.stats.verified_n, cold_start: x.cold },
|
|
70
|
+
trust_state: rec.trust_state,
|
|
71
|
+
executable: rec.trust_state === 'verified' || rec.trust_state === 'reviewed',
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
return { candidates, kognaiFirst };
|
|
75
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ports (ADR-001, ADR-002). The package imports nothing from Kognai; Kognai wires these to files and to the ClawRouter
|
|
3
|
+
* gateway. Tests wire fakes.
|
|
4
|
+
*/
|
|
5
|
+
import type { SuccessStats, ToolRecord, TrustState } from './tool';
|
|
6
|
+
export interface CatalogStore {
|
|
7
|
+
/** Hash of the index (ids, versions, schema hashes, trust, lifecycle), stamped on every route. */
|
|
8
|
+
snapshotId(): string;
|
|
9
|
+
/** Every row, every version. */
|
|
10
|
+
all(): Iterable<ToolRecord>;
|
|
11
|
+
/** The newest version of each tool id: what the router reads. */
|
|
12
|
+
latest(): Iterable<ToolRecord>;
|
|
13
|
+
get(id: string, version?: string): ToolRecord | null;
|
|
14
|
+
upsert(rows: ToolRecord[]): {
|
|
15
|
+
added: number;
|
|
16
|
+
versioned: number;
|
|
17
|
+
unchanged: number;
|
|
18
|
+
};
|
|
19
|
+
setTrust(id: string, version: string, to: TrustState, by: string, why: string): void;
|
|
20
|
+
/** Appends a trust event without a state check (ingestion: drift, revocation). */
|
|
21
|
+
recordEvent(e: TrustEvent): void;
|
|
22
|
+
events(): TrustEvent[];
|
|
23
|
+
vectors(model: string): {
|
|
24
|
+
dims: number;
|
|
25
|
+
ids: string[];
|
|
26
|
+
data: Float32Array;
|
|
27
|
+
} | null;
|
|
28
|
+
}
|
|
29
|
+
export interface EvidenceStore {
|
|
30
|
+
stats(toolId: string): SuccessStats | null;
|
|
31
|
+
}
|
|
32
|
+
export interface EmbeddingPort {
|
|
33
|
+
model: string;
|
|
34
|
+
dims: number;
|
|
35
|
+
embed(texts: string[]): Promise<Float32Array[]>;
|
|
36
|
+
}
|
|
37
|
+
export interface ClassifierLabels {
|
|
38
|
+
capabilities: string[];
|
|
39
|
+
requirements: string[];
|
|
40
|
+
complexities: string[];
|
|
41
|
+
}
|
|
42
|
+
export interface ClassifierAnswer {
|
|
43
|
+
capabilities: string[];
|
|
44
|
+
requirement?: string;
|
|
45
|
+
task_complexity?: string;
|
|
46
|
+
confidence?: number;
|
|
47
|
+
}
|
|
48
|
+
export interface ClassifierPort {
|
|
49
|
+
classify(text: string, labels: ClassifierLabels): Promise<ClassifierAnswer | null>;
|
|
50
|
+
}
|
|
51
|
+
export interface SkillRef {
|
|
52
|
+
skill_id: string;
|
|
53
|
+
version: string;
|
|
54
|
+
uplift: number;
|
|
55
|
+
evaluated: boolean;
|
|
56
|
+
}
|
|
57
|
+
export interface SkillIndex {
|
|
58
|
+
forTool(toolId: string, op: string): SkillRef[];
|
|
59
|
+
}
|
|
60
|
+
export interface Clock {
|
|
61
|
+
now(): Date;
|
|
62
|
+
}
|
|
63
|
+
export interface TrustEvent {
|
|
64
|
+
at: string;
|
|
65
|
+
id: string;
|
|
66
|
+
version: string;
|
|
67
|
+
from: TrustState;
|
|
68
|
+
to: TrustState;
|
|
69
|
+
by: string;
|
|
70
|
+
why: string;
|
|
71
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request, classification and response types (plan.md sections 3 and 4). "task_complexity" is the field name for the
|
|
3
|
+
* ToolRouter's SIMPLE to REASONING scale (decision D3, ADR-002); "tier" alone always means a ClawRouter tier.
|
|
4
|
+
*/
|
|
5
|
+
import type { BusinessTier, JSONSchema, Network, Pricing, RiskClass, TrustState } from './tool';
|
|
6
|
+
export type TaskComplexity = 'SIMPLE' | 'MEDIUM' | 'COMPLEX' | 'REASONING';
|
|
7
|
+
export declare const TASK_COMPLEXITIES: TaskComplexity[];
|
|
8
|
+
export type Requirement = 'tool' | 'no_tool' | 'no_eligible_tool' | 'clarify';
|
|
9
|
+
export declare const REQUIREMENTS: Requirement[];
|
|
10
|
+
export type Lang = 'en' | 'fr' | 'derja' | 'other';
|
|
11
|
+
export interface CallerContext {
|
|
12
|
+
caller_id: string;
|
|
13
|
+
principal_id: string;
|
|
14
|
+
kind: 'citizen' | 'swarm' | 'engine' | 'external';
|
|
15
|
+
business_tier: BusinessTier;
|
|
16
|
+
connected: Array<{
|
|
17
|
+
provider: string;
|
|
18
|
+
scopes: string[];
|
|
19
|
+
}>;
|
|
20
|
+
budget_remaining_micro?: string;
|
|
21
|
+
kognai_first: 'hard' | 'prior' | 'off';
|
|
22
|
+
/** Whose data the request is about (E7). Defaults to principal_id: a citizen reading its own owner's mail. */
|
|
23
|
+
data_owner_id?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface PlanNode {
|
|
26
|
+
node_id: string;
|
|
27
|
+
capability: string;
|
|
28
|
+
depends_on: string[];
|
|
29
|
+
risk_classes: RiskClass[];
|
|
30
|
+
args_known: string[];
|
|
31
|
+
args_missing: string[];
|
|
32
|
+
/** false for model work (summarise given text, synthesise): no shortlist is built for the node. */
|
|
33
|
+
needs_tool: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface CapabilityHit {
|
|
36
|
+
id: string;
|
|
37
|
+
confidence: number;
|
|
38
|
+
span: string;
|
|
39
|
+
}
|
|
40
|
+
export interface Classification {
|
|
41
|
+
task_complexity: TaskComplexity;
|
|
42
|
+
risk_classes: RiskClass[];
|
|
43
|
+
requirement: Requirement;
|
|
44
|
+
capabilities: string[];
|
|
45
|
+
hits: CapabilityHit[];
|
|
46
|
+
confidence: number;
|
|
47
|
+
method: 'rules' | 'model';
|
|
48
|
+
language: Lang;
|
|
49
|
+
args_known: string[];
|
|
50
|
+
/** Words that asked for analysis, comparison or synthesis (REASONING). */
|
|
51
|
+
reasoning_cues: string[];
|
|
52
|
+
/** Text recognised as quoted data (an email body, a pasted page), excluded from cue detection (T5). */
|
|
53
|
+
data_spans: number;
|
|
54
|
+
}
|
|
55
|
+
export interface RouteRequest {
|
|
56
|
+
request_id?: string;
|
|
57
|
+
text: string;
|
|
58
|
+
context: CallerContext;
|
|
59
|
+
mode?: 'discover' | 'execute_plan';
|
|
60
|
+
constraints?: {
|
|
61
|
+
max_candidates?: number;
|
|
62
|
+
max_cost_micro?: string;
|
|
63
|
+
networks?: Network[];
|
|
64
|
+
regions?: string[];
|
|
65
|
+
};
|
|
66
|
+
request_step?: 'simple' | 'product' | 'lax' | 'swarm';
|
|
67
|
+
}
|
|
68
|
+
export type ScorePart = 'fit' | 'schema' | 'success' | 'cost' | 'latency' | 'skills' | 'exploration' | 'aiax';
|
|
69
|
+
export interface Candidate {
|
|
70
|
+
tool_id: string;
|
|
71
|
+
version: string;
|
|
72
|
+
operation: string;
|
|
73
|
+
name: string;
|
|
74
|
+
input_schema: JSONSchema;
|
|
75
|
+
output_schema: JSONSchema | null;
|
|
76
|
+
score: number;
|
|
77
|
+
score_parts: Record<ScorePart, number>;
|
|
78
|
+
reasons: string[];
|
|
79
|
+
cost: Pricing;
|
|
80
|
+
skills: Array<{
|
|
81
|
+
skill_id: string;
|
|
82
|
+
version: string;
|
|
83
|
+
uplift: number;
|
|
84
|
+
}>;
|
|
85
|
+
auth_needed: {
|
|
86
|
+
kind: 'oauth' | 'api_key' | 'x402' | 'approval';
|
|
87
|
+
provider?: string;
|
|
88
|
+
scopes?: string[];
|
|
89
|
+
} | null;
|
|
90
|
+
confidence: number;
|
|
91
|
+
fallbacks: string[];
|
|
92
|
+
evidence: {
|
|
93
|
+
verified_n: number;
|
|
94
|
+
cold_start: boolean;
|
|
95
|
+
};
|
|
96
|
+
trust_state: TrustState;
|
|
97
|
+
/** false for a quarantined tool shown in discover mode: listed, never executable (threat T2). */
|
|
98
|
+
executable: boolean;
|
|
99
|
+
}
|
|
100
|
+
export interface NodeShortlist {
|
|
101
|
+
node_id: string;
|
|
102
|
+
candidates: Candidate[];
|
|
103
|
+
excluded: Record<string, number>;
|
|
104
|
+
}
|
|
105
|
+
export interface RouteResponse {
|
|
106
|
+
route_id: string;
|
|
107
|
+
created_at: string;
|
|
108
|
+
catalog_snapshot: string;
|
|
109
|
+
mode: 'discover' | 'execute_plan';
|
|
110
|
+
task_complexity: TaskComplexity;
|
|
111
|
+
risk_classes: RiskClass[];
|
|
112
|
+
requirement: Requirement;
|
|
113
|
+
classifier: {
|
|
114
|
+
method: 'rules' | 'model';
|
|
115
|
+
confidence: number;
|
|
116
|
+
};
|
|
117
|
+
plan: PlanNode[];
|
|
118
|
+
shortlists: NodeShortlist[];
|
|
119
|
+
request_step?: string;
|
|
120
|
+
notes: string[];
|
|
121
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REQUIREMENTS = exports.TASK_COMPLEXITIES = void 0;
|
|
4
|
+
exports.TASK_COMPLEXITIES = ['SIMPLE', 'MEDIUM', 'COMPLEX', 'REASONING'];
|
|
5
|
+
exports.REQUIREMENTS = ['tool', 'no_tool', 'no_eligible_tool', 'clarify'];
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tool record (plan.md section 1). One row per (tool id, version). Everything the router reads about a tool is
|
|
3
|
+
* here; the raw provider text is kept outside the record by hash only and is never shown to a model (threat T1).
|
|
4
|
+
*/
|
|
5
|
+
export type Origin = 'kognai' | 'mcp' | 'openapi' | 'x402' | 'community' | 'composio';
|
|
6
|
+
export type TrustState = 'quarantined' | 'reviewed' | 'verified' | 'deprecated' | 'revoked';
|
|
7
|
+
export type Lifecycle = 'draft' | 'active' | 'deprecated' | 'retired';
|
|
8
|
+
export type SideEffect = 'none' | 'read' | 'write' | 'publish' | 'message' | 'payment' | 'delete' | 'infra';
|
|
9
|
+
export type RiskClass = 'read_only' | 'network' | 'data_modification' | 'publish_or_message' | 'payment' | 'destructive' | 'sensitive_data' | 'privileged_infra';
|
|
10
|
+
export type PrivacyClass = 'public' | 'internal' | 'private' | 'sensitive';
|
|
11
|
+
export type BusinessTier = 'free' | 'citizen_plus' | 'citizen_pro' | 'citizen_pro_max' | 'enterprise';
|
|
12
|
+
export type Currency = 'USDC' | 'KGC' | 'USD';
|
|
13
|
+
export type Network = 'base' | 'base-sepolia' | 'solana' | 'solana-devnet' | 'offchain';
|
|
14
|
+
export type JSONSchema = Record<string, unknown>;
|
|
15
|
+
export declare const ORIGINS: Origin[];
|
|
16
|
+
export declare const TRUST_STATES: TrustState[];
|
|
17
|
+
export declare const LIFECYCLES: Lifecycle[];
|
|
18
|
+
export declare const SIDE_EFFECTS: SideEffect[];
|
|
19
|
+
export declare const RISK_CLASSES: RiskClass[];
|
|
20
|
+
/** The four classes the benchmark measures recall on (plan.md section 6). */
|
|
21
|
+
export declare const HIGH_RISK: RiskClass[];
|
|
22
|
+
export declare const PRIVACY_CLASSES: PrivacyClass[];
|
|
23
|
+
export declare const BUSINESS_TIERS: BusinessTier[];
|
|
24
|
+
export declare const NETWORKS: Network[];
|
|
25
|
+
export interface Money {
|
|
26
|
+
amount_micro: string;
|
|
27
|
+
currency: Currency;
|
|
28
|
+
network?: Network;
|
|
29
|
+
}
|
|
30
|
+
export interface Pricing {
|
|
31
|
+
model: 'free' | 'per_call' | 'per_unit' | 'unknown';
|
|
32
|
+
price?: Money;
|
|
33
|
+
unit?: string;
|
|
34
|
+
source: 'measured' | 'provider' | 'cfo_price_book' | 'unknown';
|
|
35
|
+
observed_at?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ToolOperation {
|
|
38
|
+
op: string;
|
|
39
|
+
summary: string;
|
|
40
|
+
capabilities: string[];
|
|
41
|
+
input_schema: JSONSchema;
|
|
42
|
+
output_schema: JSONSchema | null;
|
|
43
|
+
side_effects: SideEffect[];
|
|
44
|
+
risk_classes: RiskClass[];
|
|
45
|
+
scopes: string[];
|
|
46
|
+
idempotent: boolean | null;
|
|
47
|
+
url_from_user: boolean;
|
|
48
|
+
}
|
|
49
|
+
export interface Provenance {
|
|
50
|
+
source: string;
|
|
51
|
+
source_ref: string;
|
|
52
|
+
retrieved_at: string;
|
|
53
|
+
licence: string | null;
|
|
54
|
+
adapter: string;
|
|
55
|
+
adapter_version: string;
|
|
56
|
+
snapshot_sha256: string;
|
|
57
|
+
}
|
|
58
|
+
export interface SuccessStats {
|
|
59
|
+
exec_ok: number;
|
|
60
|
+
exec_n: number;
|
|
61
|
+
verified_ok: number;
|
|
62
|
+
verified_n: number;
|
|
63
|
+
distinct_principals: number;
|
|
64
|
+
window_days: number;
|
|
65
|
+
}
|
|
66
|
+
export interface ToolRecord {
|
|
67
|
+
schema_version: 1;
|
|
68
|
+
id: string;
|
|
69
|
+
version: string;
|
|
70
|
+
name: string;
|
|
71
|
+
description_safe: string;
|
|
72
|
+
description_raw_sha256: string;
|
|
73
|
+
provider: {
|
|
74
|
+
name: string;
|
|
75
|
+
url?: string;
|
|
76
|
+
kognai_owned: boolean;
|
|
77
|
+
};
|
|
78
|
+
origin: Origin;
|
|
79
|
+
category: string;
|
|
80
|
+
tags: string[];
|
|
81
|
+
operations: ToolOperation[];
|
|
82
|
+
auth: {
|
|
83
|
+
kind: 'none' | 'oauth' | 'api_key' | 'x402' | 'internal';
|
|
84
|
+
credential_ref?: string;
|
|
85
|
+
provider?: string;
|
|
86
|
+
};
|
|
87
|
+
privacy_class: PrivacyClass;
|
|
88
|
+
business_tier: BusinessTier | null;
|
|
89
|
+
regions: string[];
|
|
90
|
+
networks: Network[];
|
|
91
|
+
pricing: Pricing;
|
|
92
|
+
success: SuccessStats;
|
|
93
|
+
latency: {
|
|
94
|
+
p50_ms: number | null;
|
|
95
|
+
p95_ms: number | null;
|
|
96
|
+
n: number;
|
|
97
|
+
};
|
|
98
|
+
rate_limits: {
|
|
99
|
+
per_min?: number;
|
|
100
|
+
per_day?: number;
|
|
101
|
+
} | null;
|
|
102
|
+
health: {
|
|
103
|
+
state: 'up' | 'degraded' | 'down' | 'unknown';
|
|
104
|
+
checked_at: string | null;
|
|
105
|
+
source: 'probe' | 'experience' | 'none';
|
|
106
|
+
};
|
|
107
|
+
dependencies: string[];
|
|
108
|
+
trust_state: TrustState;
|
|
109
|
+
skill_ids: string[];
|
|
110
|
+
last_validated: string | null;
|
|
111
|
+
schema_hash: string;
|
|
112
|
+
implementation_hash: string | null;
|
|
113
|
+
lifecycle: Lifecycle;
|
|
114
|
+
aliases: string[];
|
|
115
|
+
same_as: string[];
|
|
116
|
+
product_id?: string;
|
|
117
|
+
/** The provider's base URL serves a valid AIAX manifest (/aiax/manifest.json). Optional: absent = false, and an
|
|
118
|
+
* absent field leaves the record's canonical JSON (and every hash over it) unchanged. */
|
|
119
|
+
aiax_ready?: boolean;
|
|
120
|
+
provenance: Provenance[];
|
|
121
|
+
ingested_at: string;
|
|
122
|
+
}
|
|
123
|
+
export declare const EMPTY_SUCCESS: SuccessStats;
|