@frontmcp/adapters 1.2.1 → 1.4.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/esm/package.json +6 -6
- package/esm/skills/index.mjs +742 -32
- package/package.json +6 -6
- package/skills/classifier/classification-registry.d.ts +59 -0
- package/skills/classifier/classification-registry.d.ts.map +1 -0
- package/skills/classifier/openapi-classify.d.ts +67 -0
- package/skills/classifier/openapi-classify.d.ts.map +1 -0
- package/skills/classifier/render-resource-uri.d.ts +28 -0
- package/skills/classifier/render-resource-uri.d.ts.map +1 -0
- package/skills/classifier/resource-change-notification.d.ts +41 -0
- package/skills/classifier/resource-change-notification.d.ts.map +1 -0
- package/skills/deploy/deploy-manifest.schema.d.ts +448 -0
- package/skills/deploy/deploy-manifest.schema.d.ts.map +1 -0
- package/skills/harvester/op-reference.d.ts +96 -0
- package/skills/harvester/op-reference.d.ts.map +1 -0
- package/skills/index.d.ts +6 -0
- package/skills/index.d.ts.map +1 -1
- package/skills/index.js +781 -68
- package/skills/sources/filesystem-skills.source.d.ts.map +1 -1
- package/skills/sources/saas-pull.source.d.ts.map +1 -1
- package/skills/sources/static.source.d.ts.map +1 -1
package/esm/skills/index.mjs
CHANGED
|
@@ -702,8 +702,7 @@ var NpmSource = class {
|
|
|
702
702
|
};
|
|
703
703
|
|
|
704
704
|
// libs/adapters/src/skills/sources/saas-pull.source.ts
|
|
705
|
-
import {
|
|
706
|
-
import * as path from "node:path";
|
|
705
|
+
import { dirname, ensureDir, pathResolve, readFile, writeFile } from "@frontmcp/utils";
|
|
707
706
|
var DEFAULT_CACHE_DIR = ".frontmcp/skilled-openapi";
|
|
708
707
|
var DEFAULT_PULL_TIMEOUT_MS = 3e4;
|
|
709
708
|
var SaasPullSource = class {
|
|
@@ -805,13 +804,13 @@ var SaasPullSource = class {
|
|
|
805
804
|
}
|
|
806
805
|
cachePath() {
|
|
807
806
|
const dir = this.cacheDir ?? DEFAULT_CACHE_DIR;
|
|
808
|
-
return
|
|
807
|
+
return pathResolve(dir, `${this.options.expectedAudience.replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`);
|
|
809
808
|
}
|
|
810
809
|
async persistCache(bundle) {
|
|
811
810
|
try {
|
|
812
811
|
const filePath = this.cachePath();
|
|
813
|
-
await
|
|
814
|
-
await
|
|
812
|
+
await ensureDir(dirname(filePath));
|
|
813
|
+
await writeFile(filePath, JSON.stringify(bundle));
|
|
815
814
|
} catch (e) {
|
|
816
815
|
this.logger.warn(`[saas-source] cache persist failed: ${e.message}`);
|
|
817
816
|
}
|
|
@@ -819,7 +818,7 @@ var SaasPullSource = class {
|
|
|
819
818
|
async loadCache() {
|
|
820
819
|
try {
|
|
821
820
|
const filePath = this.cachePath();
|
|
822
|
-
const raw = await
|
|
821
|
+
const raw = await readFile(filePath, "utf8");
|
|
823
822
|
return parseOverlay({ kind: "json", content: raw });
|
|
824
823
|
} catch {
|
|
825
824
|
return void 0;
|
|
@@ -837,9 +836,7 @@ var SaasPullSource = class {
|
|
|
837
836
|
};
|
|
838
837
|
|
|
839
838
|
// libs/adapters/src/skills/sources/static.source.ts
|
|
840
|
-
import {
|
|
841
|
-
import * as path2 from "node:path";
|
|
842
|
-
import { readFile } from "@frontmcp/utils";
|
|
839
|
+
import { extname, pathResolve as pathResolve2, readFile as readFile2, watchFile } from "@frontmcp/utils";
|
|
843
840
|
var StaticSource = class {
|
|
844
841
|
constructor(options, logger) {
|
|
845
842
|
this.options = options;
|
|
@@ -877,9 +874,9 @@ var StaticSource = class {
|
|
|
877
874
|
this.listeners.clear();
|
|
878
875
|
}
|
|
879
876
|
async fetchOnce() {
|
|
880
|
-
const absPath =
|
|
881
|
-
const raw = await
|
|
882
|
-
const ext =
|
|
877
|
+
const absPath = pathResolve2(this.options.path);
|
|
878
|
+
const raw = await readFile2(absPath, "utf8");
|
|
879
|
+
const ext = extname(absPath).toLowerCase();
|
|
883
880
|
let input;
|
|
884
881
|
if (ext === ".json") {
|
|
885
882
|
input = { kind: "json", content: raw };
|
|
@@ -901,9 +898,9 @@ var StaticSource = class {
|
|
|
901
898
|
}
|
|
902
899
|
}
|
|
903
900
|
beginWatch() {
|
|
904
|
-
const absPath =
|
|
901
|
+
const absPath = pathResolve2(this.options.path);
|
|
905
902
|
try {
|
|
906
|
-
this.watcher =
|
|
903
|
+
this.watcher = watchFile(absPath, () => this.scheduleRefresh());
|
|
907
904
|
} catch (e) {
|
|
908
905
|
this.logger.warn(`[static-source] fs.watch failed (${e.message}); polling at 2s instead`);
|
|
909
906
|
const tick = () => {
|
|
@@ -942,9 +939,17 @@ function createBundleSource(source, cacheDir, logger) {
|
|
|
942
939
|
}
|
|
943
940
|
|
|
944
941
|
// libs/adapters/src/skills/sources/filesystem-skills.source.ts
|
|
945
|
-
import {
|
|
946
|
-
|
|
947
|
-
|
|
942
|
+
import {
|
|
943
|
+
basename,
|
|
944
|
+
joinPath,
|
|
945
|
+
pathResolve as pathResolve3,
|
|
946
|
+
readdir,
|
|
947
|
+
readFile as readFile3,
|
|
948
|
+
realpath,
|
|
949
|
+
sep,
|
|
950
|
+
stat,
|
|
951
|
+
watchFile as watchFile2
|
|
952
|
+
} from "@frontmcp/utils";
|
|
948
953
|
var DEFAULT_OPTIONS = {
|
|
949
954
|
skillsDir: "./skills",
|
|
950
955
|
watch: true,
|
|
@@ -974,7 +979,7 @@ var FilesystemSkillsSource = class {
|
|
|
974
979
|
};
|
|
975
980
|
}
|
|
976
981
|
async start() {
|
|
977
|
-
const absRoot =
|
|
982
|
+
const absRoot = pathResolve3(this.options.skillsDir);
|
|
978
983
|
try {
|
|
979
984
|
this.rootResolved = await realpath(absRoot);
|
|
980
985
|
} catch {
|
|
@@ -1027,7 +1032,7 @@ var FilesystemSkillsSource = class {
|
|
|
1027
1032
|
} catch {
|
|
1028
1033
|
continue;
|
|
1029
1034
|
}
|
|
1030
|
-
if (!resolved.startsWith(this.rootResolved +
|
|
1035
|
+
if (!resolved.startsWith(this.rootResolved + sep) && resolved !== this.rootResolved) {
|
|
1031
1036
|
this.logger.warn(`[fs-skills] skipping ${name}: realpath escapes skillsDir`);
|
|
1032
1037
|
continue;
|
|
1033
1038
|
}
|
|
@@ -1082,18 +1087,22 @@ var FilesystemSkillsSource = class {
|
|
|
1082
1087
|
}
|
|
1083
1088
|
beginWatch() {
|
|
1084
1089
|
try {
|
|
1085
|
-
this.watcher =
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
this.
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1090
|
+
this.watcher = watchFile2(
|
|
1091
|
+
this.rootResolved,
|
|
1092
|
+
(_eventType, filename) => {
|
|
1093
|
+
if (this.stopped) return;
|
|
1094
|
+
if (!filename) {
|
|
1095
|
+
this.scheduleRescan();
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
const first = filename.split(sep)[0];
|
|
1099
|
+
if (!first || first.startsWith(".")) return;
|
|
1100
|
+
const absDir = joinPath(this.rootResolved, first);
|
|
1101
|
+
this.scheduleRefresh(absDir);
|
|
1102
|
+
},
|
|
1103
|
+
{ recursive: true }
|
|
1104
|
+
);
|
|
1105
|
+
this.watcher.onError((e) => {
|
|
1097
1106
|
this.logger.warn(`[fs-skills] fs.watch error: ${e.message}; falling back to polling`);
|
|
1098
1107
|
this.watcher?.close();
|
|
1099
1108
|
this.watcher = void 0;
|
|
@@ -1162,7 +1171,7 @@ var FilesystemSkillsSource = class {
|
|
|
1162
1171
|
};
|
|
1163
1172
|
async function loadFilesystemSkill(absDir) {
|
|
1164
1173
|
const skillMdPath = joinPath(absDir, "SKILL.md");
|
|
1165
|
-
const raw = await
|
|
1174
|
+
const raw = await readFile3(skillMdPath, "utf8");
|
|
1166
1175
|
const { frontmatter, body } = parseSkillFrontmatter(raw);
|
|
1167
1176
|
const dirName = basename(absDir);
|
|
1168
1177
|
const name = frontmatter["name"] ?? dirName;
|
|
@@ -2066,10 +2075,699 @@ var SkillAuditWriterToken = /* @__PURE__ */ Symbol.for(
|
|
|
2066
2075
|
|
|
2067
2076
|
// libs/adapters/src/skills/index.ts
|
|
2068
2077
|
import { hasSkillAuditFactory, setSkillAuditFactory } from "@frontmcp/sdk";
|
|
2078
|
+
|
|
2079
|
+
// libs/adapters/src/skills/harvester/op-reference.ts
|
|
2080
|
+
var SPEC_ID_RE = "[A-Za-z0-9][A-Za-z0-9_.-]*";
|
|
2081
|
+
var OPERATION_ID_RE = "[A-Za-z_][A-Za-z0-9_]*";
|
|
2082
|
+
var TRAILING_BOUNDARY = String.raw`(?![A-Za-z0-9_\-])(?!\.[A-Za-z0-9_])`;
|
|
2083
|
+
var REFERENCE_RE = new RegExp(
|
|
2084
|
+
String.raw`(?<![A-Za-z0-9])op:\/\/(${SPEC_ID_RE})\/(${OPERATION_ID_RE})${TRAILING_BOUNDARY}` + `|` + String.raw`\[\[op:\s?(${SPEC_ID_RE})\/(${OPERATION_ID_RE})\]\]`,
|
|
2085
|
+
"g"
|
|
2086
|
+
);
|
|
2087
|
+
function extractOpReferences(markdown) {
|
|
2088
|
+
if (typeof markdown !== "string" || markdown.length === 0) return [];
|
|
2089
|
+
const refs = [];
|
|
2090
|
+
const lineStarts = computeLineStarts(markdown);
|
|
2091
|
+
REFERENCE_RE.lastIndex = 0;
|
|
2092
|
+
let match;
|
|
2093
|
+
while ((match = REFERENCE_RE.exec(markdown)) !== null) {
|
|
2094
|
+
const offset = match.index;
|
|
2095
|
+
const [raw, uriSpec, uriOp, wikiSpec, wikiOp] = match;
|
|
2096
|
+
const isWikilink = wikiSpec !== void 0 && wikiOp !== void 0;
|
|
2097
|
+
const spec = isWikilink ? wikiSpec : uriSpec;
|
|
2098
|
+
const operationId = isWikilink ? wikiOp : uriOp;
|
|
2099
|
+
refs.push({
|
|
2100
|
+
spec,
|
|
2101
|
+
operationId,
|
|
2102
|
+
syntax: isWikilink ? "wikilink" : "uri",
|
|
2103
|
+
raw,
|
|
2104
|
+
location: offsetToLocation(offset, lineStarts)
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2107
|
+
return refs;
|
|
2108
|
+
}
|
|
2109
|
+
function buildKnownOps(entries) {
|
|
2110
|
+
const map = /* @__PURE__ */ new Map();
|
|
2111
|
+
for (const entry of entries) {
|
|
2112
|
+
const slash = entry.indexOf("/");
|
|
2113
|
+
if (slash <= 0 || slash === entry.length - 1) continue;
|
|
2114
|
+
const spec = entry.slice(0, slash);
|
|
2115
|
+
const op = entry.slice(slash + 1);
|
|
2116
|
+
let set = map.get(spec);
|
|
2117
|
+
if (!set) {
|
|
2118
|
+
set = /* @__PURE__ */ new Set();
|
|
2119
|
+
map.set(spec, set);
|
|
2120
|
+
}
|
|
2121
|
+
set.add(op);
|
|
2122
|
+
}
|
|
2123
|
+
return map;
|
|
2124
|
+
}
|
|
2125
|
+
function validateOpReferences(refs, knownOps) {
|
|
2126
|
+
const diagnostics = [];
|
|
2127
|
+
for (const ref of refs) {
|
|
2128
|
+
const specOps = knownOps.get(ref.spec);
|
|
2129
|
+
if (!specOps) {
|
|
2130
|
+
diagnostics.push({
|
|
2131
|
+
ref,
|
|
2132
|
+
kind: "unknown-spec",
|
|
2133
|
+
message: `Unknown OpenAPI spec "${ref.spec}" referenced as ${ref.raw}`,
|
|
2134
|
+
suggestions: Array.from(knownOps.keys()).sort()
|
|
2135
|
+
});
|
|
2136
|
+
continue;
|
|
2137
|
+
}
|
|
2138
|
+
if (!specOps.has(ref.operationId)) {
|
|
2139
|
+
diagnostics.push({
|
|
2140
|
+
ref,
|
|
2141
|
+
kind: "unknown-operation",
|
|
2142
|
+
message: `Unknown operationId "${ref.operationId}" in spec "${ref.spec}" (${ref.raw})`,
|
|
2143
|
+
suggestions: suggestSimilarOps(ref.operationId, specOps)
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
return diagnostics;
|
|
2148
|
+
}
|
|
2149
|
+
function dedupeOpReferences(refs) {
|
|
2150
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2151
|
+
const result = [];
|
|
2152
|
+
for (const ref of refs) {
|
|
2153
|
+
const key = `${ref.spec}/${ref.operationId}`;
|
|
2154
|
+
if (seen.has(key)) continue;
|
|
2155
|
+
seen.add(key);
|
|
2156
|
+
result.push({ spec: ref.spec, operationId: ref.operationId });
|
|
2157
|
+
}
|
|
2158
|
+
return result;
|
|
2159
|
+
}
|
|
2160
|
+
function computeLineStarts(source) {
|
|
2161
|
+
const starts = [0];
|
|
2162
|
+
for (let i = 0; i < source.length; i++) {
|
|
2163
|
+
if (source.charCodeAt(i) === 10) starts.push(i + 1);
|
|
2164
|
+
}
|
|
2165
|
+
return starts;
|
|
2166
|
+
}
|
|
2167
|
+
function offsetToLocation(offset, lineStarts) {
|
|
2168
|
+
let lo = 0;
|
|
2169
|
+
let hi = lineStarts.length - 1;
|
|
2170
|
+
while (lo < hi) {
|
|
2171
|
+
const mid = lo + hi + 1 >>> 1;
|
|
2172
|
+
if (lineStarts[mid] <= offset) lo = mid;
|
|
2173
|
+
else hi = mid - 1;
|
|
2174
|
+
}
|
|
2175
|
+
const line = lo + 1;
|
|
2176
|
+
const column = offset - lineStarts[lo] + 1;
|
|
2177
|
+
return { line, column, offset };
|
|
2178
|
+
}
|
|
2179
|
+
function suggestSimilarOps(query, known) {
|
|
2180
|
+
if (known.size === 0) return [];
|
|
2181
|
+
const q = query.toLowerCase();
|
|
2182
|
+
const candidates = Array.from(known);
|
|
2183
|
+
const prefix = candidates.filter((c) => c.toLowerCase().startsWith(q));
|
|
2184
|
+
const substring = candidates.filter((c) => !prefix.includes(c) && c.toLowerCase().includes(q));
|
|
2185
|
+
const close = candidates.filter((c) => !prefix.includes(c) && !substring.includes(c)).filter((c) => levenshtein(q, c.toLowerCase()) <= Math.max(2, Math.floor(q.length / 4)));
|
|
2186
|
+
return [...prefix, ...substring, ...close].slice(0, 3);
|
|
2187
|
+
}
|
|
2188
|
+
function levenshtein(a, b) {
|
|
2189
|
+
if (a === b) return 0;
|
|
2190
|
+
if (a.length === 0) return b.length;
|
|
2191
|
+
if (b.length === 0) return a.length;
|
|
2192
|
+
let prev = new Array(b.length + 1);
|
|
2193
|
+
let curr = new Array(b.length + 1);
|
|
2194
|
+
for (let j = 0; j <= b.length; j++) prev[j] = j;
|
|
2195
|
+
for (let i = 1; i <= a.length; i++) {
|
|
2196
|
+
curr[0] = i;
|
|
2197
|
+
for (let j = 1; j <= b.length; j++) {
|
|
2198
|
+
const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
|
|
2199
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
2200
|
+
}
|
|
2201
|
+
[prev, curr] = [curr, prev];
|
|
2202
|
+
}
|
|
2203
|
+
return prev[b.length];
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
// libs/adapters/src/skills/deploy/deploy-manifest.schema.ts
|
|
2207
|
+
import { z as z3 } from "@frontmcp/lazy-zod";
|
|
2208
|
+
var IDENT_RE = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
2209
|
+
var SPEC_ID_RE2 = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
|
|
2210
|
+
var SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
2211
|
+
var BINDING_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
2212
|
+
var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
2213
|
+
var serverInfoSchema = z3.object({
|
|
2214
|
+
name: z3.string().min(1).max(128),
|
|
2215
|
+
version: z3.string().min(1).max(64),
|
|
2216
|
+
title: z3.string().min(1).max(256).optional()
|
|
2217
|
+
}).strict();
|
|
2218
|
+
var serverSchema = z3.object({
|
|
2219
|
+
info: serverInfoSchema,
|
|
2220
|
+
/**
|
|
2221
|
+
* Top-level server instructions injected at `initialize` time. Picked up
|
|
2222
|
+
* by the existing `skillsConfig.injectInstructions` machinery (defaults
|
|
2223
|
+
* to `append` so the skill catalog summary follows).
|
|
2224
|
+
*/
|
|
2225
|
+
instructions: z3.string().max(16 * 1024).optional()
|
|
2226
|
+
}).strict();
|
|
2227
|
+
var runtimeSchema = z3.object({
|
|
2228
|
+
/** Only `cloudflare-worker` in v1. Future: `vercel-edge`, `deno-deploy`. */
|
|
2229
|
+
target: z3.literal("cloudflare-worker"),
|
|
2230
|
+
compatibilityDate: z3.string().regex(ISO_DATE_RE, "compatibilityDate must be ISO-8601 (YYYY-MM-DD)"),
|
|
2231
|
+
/** Worker compatibility flags (e.g. `nodejs_compat`). */
|
|
2232
|
+
compatibilityFlags: z3.array(z3.string().min(1).max(128)).max(32).optional()
|
|
2233
|
+
}).strict();
|
|
2234
|
+
var specSourceSchema = z3.union([
|
|
2235
|
+
/** Local file path or HTTPS URL to a single spec. Spec id = filename stem. */
|
|
2236
|
+
z3.string().min(1).max(2048),
|
|
2237
|
+
z3.object({
|
|
2238
|
+
id: z3.string().min(1).max(128).regex(SPEC_ID_RE2, "spec id must be a URI-safe identifier"),
|
|
2239
|
+
spec: z3.string().min(1).max(2048),
|
|
2240
|
+
baseUrl: z3.string().url("baseUrl must be a valid URL").optional(),
|
|
2241
|
+
/** Optional namespace override for AgentScript bindings (default = `id`). */
|
|
2242
|
+
bindingName: z3.string().min(1).max(64).regex(IDENT_RE).optional()
|
|
2243
|
+
}).strict()
|
|
2244
|
+
]);
|
|
2245
|
+
var specsSchema = z3.union([
|
|
2246
|
+
/** Single directory path; the loader auto-discovers `*.yaml` / `*.json`. */
|
|
2247
|
+
z3.string().min(1).max(2048),
|
|
2248
|
+
/** Explicit list (mixed strings and detail objects allowed). */
|
|
2249
|
+
z3.array(specSourceSchema).max(64)
|
|
2250
|
+
]);
|
|
2251
|
+
var skillsSchema = z3.object({
|
|
2252
|
+
/** Directory under the manifest. Defaults to `./skills/`. */
|
|
2253
|
+
source: z3.string().min(1).max(2048).default("./skills/"),
|
|
2254
|
+
/**
|
|
2255
|
+
* IDs of skills the worker pre-loads into every `execute()` regardless
|
|
2256
|
+
* of which skills the agent passed. Mirrors the per-skill
|
|
2257
|
+
* `alwaysLoad: true` frontmatter, but lets the server force it.
|
|
2258
|
+
*/
|
|
2259
|
+
alwaysLoad: z3.array(z3.string().min(1).max(128)).max(32).optional(),
|
|
2260
|
+
/** Optional tag filter applied at deploy time. */
|
|
2261
|
+
tags: z3.object({
|
|
2262
|
+
include: z3.array(z3.string().min(1).max(64)).max(64).optional(),
|
|
2263
|
+
exclude: z3.array(z3.string().min(1).max(64)).max(64).optional()
|
|
2264
|
+
}).strict().optional()
|
|
2265
|
+
}).strict();
|
|
2266
|
+
var tagSchema = z3.object({
|
|
2267
|
+
name: z3.string().min(1).max(64),
|
|
2268
|
+
description: z3.string().min(1).max(512).optional()
|
|
2269
|
+
}).strict();
|
|
2270
|
+
var classificationRuleSchema = z3.object({
|
|
2271
|
+
/**
|
|
2272
|
+
* Match pattern: METHOD followed by a path-glob, e.g.
|
|
2273
|
+
* `POST <star>/reset-<star>` (where <star> is the literal `*` glob).
|
|
2274
|
+
* Written this way to avoid prematurely closing the JSDoc with star-slash.
|
|
2275
|
+
*/
|
|
2276
|
+
match: z3.string().min(1).max(256),
|
|
2277
|
+
/**
|
|
2278
|
+
* Override the MCP surface for matching operations: tool (default for
|
|
2279
|
+
* mutations), resource (default for GET list / GET path-param), or both
|
|
2280
|
+
* (default for GET path-param). Omit to leave the classifier's default.
|
|
2281
|
+
*/
|
|
2282
|
+
expose: z3.enum(["tool", "resource", "both"]).optional(),
|
|
2283
|
+
/**
|
|
2284
|
+
* Resource-update emit target. `self` (mutation on its own resource),
|
|
2285
|
+
* `parent` (mutation that affects the parent collection / parent path),
|
|
2286
|
+
* or `none` (suppress). Defaults from the HTTP-semantics classifier.
|
|
2287
|
+
*/
|
|
2288
|
+
emits: z3.enum(["self", "parent", "none"]).optional()
|
|
2289
|
+
}).strict();
|
|
2290
|
+
var classificationSchema = z3.object({
|
|
2291
|
+
rules: z3.array(classificationRuleSchema).max(64).optional()
|
|
2292
|
+
}).strict();
|
|
2293
|
+
var durableObjectBindingSchema = z3.object({
|
|
2294
|
+
binding: z3.string().min(1).max(128).regex(BINDING_NAME_RE),
|
|
2295
|
+
className: z3.string().min(1).max(128).regex(IDENT_RE),
|
|
2296
|
+
scriptName: z3.string().min(1).max(128).optional()
|
|
2297
|
+
}).strict();
|
|
2298
|
+
var d1BindingSchema = z3.object({
|
|
2299
|
+
binding: z3.string().min(1).max(128).regex(BINDING_NAME_RE),
|
|
2300
|
+
databaseName: z3.string().min(1).max(256),
|
|
2301
|
+
databaseId: z3.string().min(1).max(128)
|
|
2302
|
+
}).strict();
|
|
2303
|
+
var kvBindingSchema = z3.object({
|
|
2304
|
+
binding: z3.string().min(1).max(128).regex(BINDING_NAME_RE),
|
|
2305
|
+
id: z3.string().min(1).max(128)
|
|
2306
|
+
}).strict();
|
|
2307
|
+
var r2BindingSchema = z3.object({
|
|
2308
|
+
binding: z3.string().min(1).max(128).regex(BINDING_NAME_RE),
|
|
2309
|
+
bucketName: z3.string().min(1).max(256)
|
|
2310
|
+
}).strict();
|
|
2311
|
+
var bindingsSchema = z3.object({
|
|
2312
|
+
durableObjects: z3.array(durableObjectBindingSchema).max(32).optional(),
|
|
2313
|
+
d1Databases: z3.array(d1BindingSchema).max(16).optional(),
|
|
2314
|
+
kvNamespaces: z3.array(kvBindingSchema).max(32).optional(),
|
|
2315
|
+
r2Buckets: z3.array(r2BindingSchema).max(16).optional(),
|
|
2316
|
+
/** Plaintext vars surfaced as `env.<name>` on the Worker. */
|
|
2317
|
+
vars: z3.record(z3.string().min(1).max(128), z3.string().max(4096)).optional()
|
|
2318
|
+
}).strict();
|
|
2319
|
+
var trustRootSchema = z3.object({
|
|
2320
|
+
/** Key id — matches the JWS `kid` header on the signed bundle. */
|
|
2321
|
+
kid: z3.string().min(1).max(128),
|
|
2322
|
+
/** Name of the secret binding holding the PEM-encoded public key. */
|
|
2323
|
+
publicKeySecret: z3.string().min(1).max(128).regex(SECRET_NAME_RE)
|
|
2324
|
+
}).strict();
|
|
2325
|
+
var replayGuardSchema = z3.object({
|
|
2326
|
+
/** Acceptable clock skew; rejects bundles older than this. */
|
|
2327
|
+
windowSeconds: z3.number().int().min(10).max(3600).default(300),
|
|
2328
|
+
/** KV binding name that stores nonces for replay protection. */
|
|
2329
|
+
nonceKv: z3.string().min(1).max(128).regex(BINDING_NAME_RE)
|
|
2330
|
+
}).strict();
|
|
2331
|
+
var signingSchema = z3.object({
|
|
2332
|
+
algorithm: z3.enum(["ed25519", "rs256"]),
|
|
2333
|
+
trustRoots: z3.array(trustRootSchema).min(1).max(8),
|
|
2334
|
+
replay: replayGuardSchema
|
|
2335
|
+
}).strict();
|
|
2336
|
+
var fronteggAuthSchema = z3.object({
|
|
2337
|
+
tenantResolver: z3.enum(["subdomain", "header", "jwt-claim"]).default("subdomain"),
|
|
2338
|
+
audience: z3.string().min(1).max(256),
|
|
2339
|
+
/** Secret name holding the issuer URL. */
|
|
2340
|
+
issuerSecret: z3.string().min(1).max(128).regex(SECRET_NAME_RE)
|
|
2341
|
+
}).strict();
|
|
2342
|
+
var oauthAuthSchema = z3.object({
|
|
2343
|
+
issuer: z3.string().url(),
|
|
2344
|
+
audience: z3.string().min(1).max(256),
|
|
2345
|
+
/** Optional secret name for client credentials (machine-to-machine). */
|
|
2346
|
+
credentialsSecret: z3.string().min(1).max(128).regex(SECRET_NAME_RE).optional()
|
|
2347
|
+
}).strict();
|
|
2348
|
+
var apiKeyAuthSchema = z3.object({
|
|
2349
|
+
/** Header name carrying the key (e.g. `X-API-Key`). */
|
|
2350
|
+
header: z3.string().min(1).max(64).default("X-API-Key"),
|
|
2351
|
+
/** Secret name holding the allowlist (newline-separated keys). */
|
|
2352
|
+
allowlistSecret: z3.string().min(1).max(128).regex(SECRET_NAME_RE)
|
|
2353
|
+
}).strict();
|
|
2354
|
+
var authSchema = z3.discriminatedUnion("provider", [
|
|
2355
|
+
z3.object({ provider: z3.literal("none") }).strict(),
|
|
2356
|
+
z3.object({ provider: z3.literal("frontegg"), frontegg: fronteggAuthSchema }).strict(),
|
|
2357
|
+
z3.object({ provider: z3.literal("oauth"), oauth: oauthAuthSchema }).strict(),
|
|
2358
|
+
z3.object({ provider: z3.literal("apiKey"), apiKey: apiKeyAuthSchema }).strict()
|
|
2359
|
+
]);
|
|
2360
|
+
var secretSchema = z3.object({
|
|
2361
|
+
name: z3.string().min(1).max(128).regex(SECRET_NAME_RE),
|
|
2362
|
+
required: z3.boolean().default(true),
|
|
2363
|
+
description: z3.string().min(1).max(512).optional()
|
|
2364
|
+
}).strict();
|
|
2365
|
+
var environmentOverlaySchema = z3.object({
|
|
2366
|
+
server: serverSchema.partial().optional(),
|
|
2367
|
+
specs: specsSchema.optional(),
|
|
2368
|
+
skills: skillsSchema.partial().optional(),
|
|
2369
|
+
classification: classificationSchema.optional(),
|
|
2370
|
+
bindings: bindingsSchema.optional(),
|
|
2371
|
+
auth: authSchema.optional(),
|
|
2372
|
+
vars: z3.record(z3.string().min(1).max(128), z3.string().max(4096)).optional()
|
|
2373
|
+
}).strict();
|
|
2374
|
+
var deployManifestSchema = z3.object({
|
|
2375
|
+
/** Optional JSON Schema URL — accepted for editor autocompletion. */
|
|
2376
|
+
$schema: z3.string().url().optional(),
|
|
2377
|
+
/** Schema version. Only `1` accepted in v1.3. */
|
|
2378
|
+
version: z3.literal(1),
|
|
2379
|
+
/** Project name (for logs + the Worker's `worker.name`). */
|
|
2380
|
+
name: z3.string().min(1).max(128).regex(IDENT_RE),
|
|
2381
|
+
runtime: runtimeSchema,
|
|
2382
|
+
server: serverSchema,
|
|
2383
|
+
specs: specsSchema,
|
|
2384
|
+
skills: skillsSchema.default({ source: "./skills/" }),
|
|
2385
|
+
tags: z3.array(tagSchema).max(64).optional(),
|
|
2386
|
+
classification: classificationSchema.optional(),
|
|
2387
|
+
bindings: bindingsSchema,
|
|
2388
|
+
signing: signingSchema,
|
|
2389
|
+
auth: authSchema,
|
|
2390
|
+
secrets: z3.array(secretSchema).max(64).optional(),
|
|
2391
|
+
environments: z3.record(z3.string().min(1).max(64).regex(IDENT_RE), environmentOverlaySchema).optional()
|
|
2392
|
+
}).strict();
|
|
2393
|
+
function applyEnvironmentOverlay(base, overlay) {
|
|
2394
|
+
const effectiveBindings = overlay.bindings ?? base.bindings;
|
|
2395
|
+
const mergedBindings = overlay.vars !== void 0 ? { ...effectiveBindings, vars: { ...effectiveBindings.vars ?? {}, ...overlay.vars } } : effectiveBindings;
|
|
2396
|
+
return {
|
|
2397
|
+
...base,
|
|
2398
|
+
server: overlay.server ? { ...base.server, ...overlay.server } : base.server,
|
|
2399
|
+
specs: overlay.specs ?? base.specs,
|
|
2400
|
+
skills: overlay.skills ? { ...base.skills, ...overlay.skills } : base.skills,
|
|
2401
|
+
classification: overlay.classification ?? base.classification,
|
|
2402
|
+
bindings: mergedBindings,
|
|
2403
|
+
auth: overlay.auth ?? base.auth
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
function validateEffectiveManifest(manifest, label, errors) {
|
|
2407
|
+
const declaredSecrets = new Set((manifest.secrets ?? []).map((s) => s.name));
|
|
2408
|
+
const refs = [];
|
|
2409
|
+
if (manifest.auth.provider === "frontegg") {
|
|
2410
|
+
refs.push({ where: "auth.frontegg.issuerSecret", name: manifest.auth.frontegg.issuerSecret });
|
|
2411
|
+
} else if (manifest.auth.provider === "oauth" && manifest.auth.oauth.credentialsSecret) {
|
|
2412
|
+
refs.push({ where: "auth.oauth.credentialsSecret", name: manifest.auth.oauth.credentialsSecret });
|
|
2413
|
+
} else if (manifest.auth.provider === "apiKey") {
|
|
2414
|
+
refs.push({ where: "auth.apiKey.allowlistSecret", name: manifest.auth.apiKey.allowlistSecret });
|
|
2415
|
+
}
|
|
2416
|
+
for (const root of manifest.signing.trustRoots) {
|
|
2417
|
+
refs.push({ where: `signing.trustRoots[kid=${root.kid}].publicKeySecret`, name: root.publicKeySecret });
|
|
2418
|
+
}
|
|
2419
|
+
for (const ref of refs) {
|
|
2420
|
+
if (!declaredSecrets.has(ref.name)) {
|
|
2421
|
+
errors.push(`${label}Secret "${ref.name}" referenced at ${ref.where} is not declared in secrets[]`);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
const declaredKv = new Set((manifest.bindings.kvNamespaces ?? []).map((kv) => kv.binding));
|
|
2425
|
+
if (!declaredKv.has(manifest.signing.replay.nonceKv)) {
|
|
2426
|
+
errors.push(
|
|
2427
|
+
`${label}signing.replay.nonceKv "${manifest.signing.replay.nonceKv}" does not match any bindings.kvNamespaces[].binding`
|
|
2428
|
+
);
|
|
2429
|
+
}
|
|
2430
|
+
for (const id of manifest.skills.alwaysLoad ?? []) {
|
|
2431
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(id)) {
|
|
2432
|
+
errors.push(`${label}skills.alwaysLoad entry "${id}" is not a valid kebab-case skill id`);
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
if (manifest.tags) {
|
|
2436
|
+
const declaredTags = new Set(manifest.tags.map((t) => t.name));
|
|
2437
|
+
const tagFilter = manifest.skills.tags;
|
|
2438
|
+
for (const t of [...tagFilter?.include ?? [], ...tagFilter?.exclude ?? []]) {
|
|
2439
|
+
if (!declaredTags.has(t)) {
|
|
2440
|
+
errors.push(`${label}skills.tags references unknown tag "${t}" (not in tags[])`);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
function crossValidateManifest(manifest) {
|
|
2446
|
+
const errors = [];
|
|
2447
|
+
validateEffectiveManifest(manifest, "", errors);
|
|
2448
|
+
for (const [envName, overlay] of Object.entries(manifest.environments ?? {})) {
|
|
2449
|
+
const effective = applyEnvironmentOverlay(manifest, overlay);
|
|
2450
|
+
validateEffectiveManifest(effective, `environments.${envName}: `, errors);
|
|
2451
|
+
}
|
|
2452
|
+
return errors.length === 0 ? { ok: true } : { ok: false, errors };
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
// libs/adapters/src/skills/classifier/openapi-classify.ts
|
|
2456
|
+
function classifyOperations(specId, ops) {
|
|
2457
|
+
if (!specId || specId.length === 0) {
|
|
2458
|
+
throw new Error("classifyOperations: specId is required");
|
|
2459
|
+
}
|
|
2460
|
+
if (!Array.isArray(ops)) return [];
|
|
2461
|
+
const pathsWithGet = /* @__PURE__ */ new Set();
|
|
2462
|
+
for (const op of ops) {
|
|
2463
|
+
if (op && typeof op.path === "string" && typeof op.method === "string" && op.method.toUpperCase() === "GET") {
|
|
2464
|
+
pathsWithGet.add(op.path);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
return ops.map((op) => classifyOne(specId, op, pathsWithGet));
|
|
2468
|
+
}
|
|
2469
|
+
function classifyOne(specId, op, pathsWithGet) {
|
|
2470
|
+
const method = normaliseMethod(op.method);
|
|
2471
|
+
const path = op.path;
|
|
2472
|
+
const hasTerminalParam = pathHasTerminalParam(path);
|
|
2473
|
+
const parent = parentPath(path);
|
|
2474
|
+
const thisIsResource = pathsWithGet.has(path);
|
|
2475
|
+
const parentIsResource = parent !== null && pathsWithGet.has(parent);
|
|
2476
|
+
const selfUri = pathToResourceUri(specId, path);
|
|
2477
|
+
const parentUri = parent !== null ? pathToResourceUri(specId, parent) : null;
|
|
2478
|
+
if (method === "GET") {
|
|
2479
|
+
if (hasTerminalParam) {
|
|
2480
|
+
return {
|
|
2481
|
+
operationId: op.operationId,
|
|
2482
|
+
method,
|
|
2483
|
+
path,
|
|
2484
|
+
specId,
|
|
2485
|
+
expose: "both",
|
|
2486
|
+
resourceUriTemplate: selfUri
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2489
|
+
return {
|
|
2490
|
+
operationId: op.operationId,
|
|
2491
|
+
method,
|
|
2492
|
+
path,
|
|
2493
|
+
specId,
|
|
2494
|
+
expose: "resource",
|
|
2495
|
+
resourceUriTemplate: selfUri
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
const base = {
|
|
2499
|
+
operationId: op.operationId,
|
|
2500
|
+
method,
|
|
2501
|
+
path,
|
|
2502
|
+
specId,
|
|
2503
|
+
expose: "tool"
|
|
2504
|
+
// `resourceUriTemplate` left undefined for mutations — the emit target
|
|
2505
|
+
// is the authoritative thing to look at.
|
|
2506
|
+
};
|
|
2507
|
+
if (method === "POST") {
|
|
2508
|
+
if (thisIsResource && !hasTerminalParam) {
|
|
2509
|
+
return { ...base, emit: { kind: "listChanged", pathTemplate: path, resourceUriTemplate: selfUri } };
|
|
2510
|
+
}
|
|
2511
|
+
if (thisIsResource && hasTerminalParam) {
|
|
2512
|
+
return { ...base, emit: { kind: "updated", pathTemplate: path, resourceUriTemplate: selfUri } };
|
|
2513
|
+
}
|
|
2514
|
+
if (parentIsResource && parentUri !== null && parent !== null) {
|
|
2515
|
+
return { ...base, emit: { kind: "updated", pathTemplate: parent, resourceUriTemplate: parentUri } };
|
|
2516
|
+
}
|
|
2517
|
+
return base;
|
|
2518
|
+
}
|
|
2519
|
+
if (method === "PUT" || method === "PATCH") {
|
|
2520
|
+
if (thisIsResource) {
|
|
2521
|
+
return { ...base, emit: { kind: "updated", pathTemplate: path, resourceUriTemplate: selfUri } };
|
|
2522
|
+
}
|
|
2523
|
+
if (parentIsResource && parentUri !== null && parent !== null) {
|
|
2524
|
+
return { ...base, emit: { kind: "updated", pathTemplate: parent, resourceUriTemplate: parentUri } };
|
|
2525
|
+
}
|
|
2526
|
+
return base;
|
|
2527
|
+
}
|
|
2528
|
+
if (method === "DELETE") {
|
|
2529
|
+
if (hasTerminalParam && parentIsResource && parentUri !== null && parent !== null) {
|
|
2530
|
+
return { ...base, emit: { kind: "listChanged", pathTemplate: parent, resourceUriTemplate: parentUri } };
|
|
2531
|
+
}
|
|
2532
|
+
if (!hasTerminalParam && thisIsResource) {
|
|
2533
|
+
return { ...base, emit: { kind: "listChanged", pathTemplate: path, resourceUriTemplate: selfUri } };
|
|
2534
|
+
}
|
|
2535
|
+
return base;
|
|
2536
|
+
}
|
|
2537
|
+
return base;
|
|
2538
|
+
}
|
|
2539
|
+
function applyClassificationOverrides(classified, rules) {
|
|
2540
|
+
if (!Array.isArray(rules) || rules.length === 0) return classified.slice();
|
|
2541
|
+
const compiled = rules.map((rule) => ({ rule, matcher: compileMatcher(rule.match) }));
|
|
2542
|
+
return classified.map((op) => {
|
|
2543
|
+
for (const { rule, matcher } of compiled) {
|
|
2544
|
+
if (!matcher(op)) continue;
|
|
2545
|
+
return applyOne(op, rule);
|
|
2546
|
+
}
|
|
2547
|
+
return op;
|
|
2548
|
+
});
|
|
2549
|
+
}
|
|
2550
|
+
var KNOWN_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "TRACE"]);
|
|
2551
|
+
function normaliseMethod(m) {
|
|
2552
|
+
const up = (m ?? "").toUpperCase();
|
|
2553
|
+
return KNOWN_METHODS.has(up) ? up : up;
|
|
2554
|
+
}
|
|
2555
|
+
function pathHasTerminalParam(path) {
|
|
2556
|
+
if (typeof path !== "string" || path.length === 0) return false;
|
|
2557
|
+
const segments = path.split("/").filter((s) => s.length > 0);
|
|
2558
|
+
if (segments.length === 0) return false;
|
|
2559
|
+
const last = segments[segments.length - 1];
|
|
2560
|
+
return last.startsWith("{") && last.endsWith("}") && last.length >= 3;
|
|
2561
|
+
}
|
|
2562
|
+
function parentPath(path) {
|
|
2563
|
+
if (typeof path !== "string" || path.length === 0) return null;
|
|
2564
|
+
const segments = path.split("/").filter((s) => s.length > 0);
|
|
2565
|
+
if (segments.length <= 1) return null;
|
|
2566
|
+
return "/" + segments.slice(0, -1).join("/");
|
|
2567
|
+
}
|
|
2568
|
+
function pathToResourceUri(specId, path) {
|
|
2569
|
+
const cleaned = path.startsWith("/") ? path.slice(1) : path;
|
|
2570
|
+
return `mcp+op://${specId}/${cleaned}`;
|
|
2571
|
+
}
|
|
2572
|
+
function compileMatcher(match) {
|
|
2573
|
+
const trimmed = (match ?? "").trim();
|
|
2574
|
+
const sep2 = trimmed.indexOf(" ");
|
|
2575
|
+
if (sep2 <= 0 || sep2 === trimmed.length - 1) {
|
|
2576
|
+
return () => false;
|
|
2577
|
+
}
|
|
2578
|
+
const methodPattern = trimmed.slice(0, sep2).toUpperCase();
|
|
2579
|
+
const pathPattern = trimmed.slice(sep2 + 1);
|
|
2580
|
+
const pathRe = globToRegExp(pathPattern);
|
|
2581
|
+
return (op) => {
|
|
2582
|
+
if (methodPattern !== "*" && methodPattern !== op.method) return false;
|
|
2583
|
+
return pathRe.test(op.path);
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
function globToRegExp(glob) {
|
|
2587
|
+
const STAR_STAR = "\0";
|
|
2588
|
+
const STAR = "";
|
|
2589
|
+
const replaced = glob.replace(/\*\*/g, STAR_STAR).replace(/\*/g, STAR);
|
|
2590
|
+
const escaped = replaced.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
2591
|
+
const expanded = escaped.split(STAR_STAR).join(".*").split(STAR).join("[^/]*");
|
|
2592
|
+
return new RegExp(`^${expanded}$`);
|
|
2593
|
+
}
|
|
2594
|
+
function defaultEmitKindForMethod(method) {
|
|
2595
|
+
return method === "DELETE" ? "listChanged" : "updated";
|
|
2596
|
+
}
|
|
2597
|
+
function applyOne(op, rule) {
|
|
2598
|
+
let next = { ...op };
|
|
2599
|
+
if (rule.expose !== void 0) {
|
|
2600
|
+
next = { ...next, expose: rule.expose };
|
|
2601
|
+
}
|
|
2602
|
+
if (rule.emits !== void 0) {
|
|
2603
|
+
if (rule.emits === "none") {
|
|
2604
|
+
const { emit: _drop, ...rest } = next;
|
|
2605
|
+
next = rest;
|
|
2606
|
+
} else if (rule.emits === "self") {
|
|
2607
|
+
const target = pathToResourceUri(op.specId, op.path);
|
|
2608
|
+
next = {
|
|
2609
|
+
...next,
|
|
2610
|
+
emit: {
|
|
2611
|
+
// Preserve the classifier's original kind if it set one; otherwise
|
|
2612
|
+
// derive from the HTTP method so a DELETE override doesn't end up
|
|
2613
|
+
// emitting `updated` on its own (now-gone) URI.
|
|
2614
|
+
kind: next.emit?.kind ?? defaultEmitKindForMethod(op.method),
|
|
2615
|
+
pathTemplate: op.path,
|
|
2616
|
+
resourceUriTemplate: target
|
|
2617
|
+
}
|
|
2618
|
+
};
|
|
2619
|
+
} else if (rule.emits === "parent") {
|
|
2620
|
+
const parent = parentPath(op.path);
|
|
2621
|
+
if (parent !== null) {
|
|
2622
|
+
next = {
|
|
2623
|
+
...next,
|
|
2624
|
+
emit: {
|
|
2625
|
+
kind: next.emit?.kind ?? defaultEmitKindForMethod(op.method),
|
|
2626
|
+
pathTemplate: parent,
|
|
2627
|
+
resourceUriTemplate: pathToResourceUri(op.specId, parent)
|
|
2628
|
+
}
|
|
2629
|
+
};
|
|
2630
|
+
} else {
|
|
2631
|
+
const { emit: _drop, ...rest } = next;
|
|
2632
|
+
next = rest;
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
return next;
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
// libs/adapters/src/skills/classifier/render-resource-uri.ts
|
|
2640
|
+
var TEMPLATE_PLACEHOLDER_RE = /\{([^{}]+)\}/g;
|
|
2641
|
+
function renderResourceUri(template, args) {
|
|
2642
|
+
if (typeof template !== "string" || template.length === 0) {
|
|
2643
|
+
return { ok: false, missing: [] };
|
|
2644
|
+
}
|
|
2645
|
+
const argObj = args !== null && typeof args === "object" ? args : {};
|
|
2646
|
+
const missing = [];
|
|
2647
|
+
const uri = template.replace(TEMPLATE_PLACEHOLDER_RE, (_match, name) => {
|
|
2648
|
+
const value = argObj[name];
|
|
2649
|
+
if (value === void 0 || value === null) {
|
|
2650
|
+
missing.push(name);
|
|
2651
|
+
return "";
|
|
2652
|
+
}
|
|
2653
|
+
if (typeof value === "object") {
|
|
2654
|
+
missing.push(name);
|
|
2655
|
+
return "";
|
|
2656
|
+
}
|
|
2657
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
2658
|
+
missing.push(name);
|
|
2659
|
+
return "";
|
|
2660
|
+
}
|
|
2661
|
+
return encodeURIComponent(String(value));
|
|
2662
|
+
});
|
|
2663
|
+
if (missing.length > 0) return { ok: false, missing };
|
|
2664
|
+
return { ok: true, uri };
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
// libs/adapters/src/skills/classifier/resource-change-notification.ts
|
|
2668
|
+
function buildResourceChangeNotification(classification, args) {
|
|
2669
|
+
const emit = classification.emit;
|
|
2670
|
+
if (!emit) return { notification: null, reason: "no-emit" };
|
|
2671
|
+
if (emit.kind === "listChanged") {
|
|
2672
|
+
const rendered2 = renderResourceUri(emit.resourceUriTemplate, args);
|
|
2673
|
+
if (!rendered2.ok) {
|
|
2674
|
+
return { notification: null, reason: "unresolved-template", missing: rendered2.missing };
|
|
2675
|
+
}
|
|
2676
|
+
return { notification: { method: "notifications/resources/list_changed" } };
|
|
2677
|
+
}
|
|
2678
|
+
const rendered = renderResourceUri(emit.resourceUriTemplate, args);
|
|
2679
|
+
if (!rendered.ok) {
|
|
2680
|
+
return { notification: null, reason: "unresolved-template", missing: rendered.missing };
|
|
2681
|
+
}
|
|
2682
|
+
return {
|
|
2683
|
+
notification: {
|
|
2684
|
+
method: "notifications/resources/updated",
|
|
2685
|
+
params: { uri: rendered.uri }
|
|
2686
|
+
}
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
// libs/adapters/src/skills/classifier/classification-registry.ts
|
|
2691
|
+
var ClassificationRegistry = class {
|
|
2692
|
+
byToolName = /* @__PURE__ */ new Map();
|
|
2693
|
+
/**
|
|
2694
|
+
* Register (or replace) a classification for a tool name. Returns the
|
|
2695
|
+
* previous classification if one existed.
|
|
2696
|
+
*
|
|
2697
|
+
* Useful for hot-reload: the deploy pipeline re-builds classifications
|
|
2698
|
+
* from the new bundle and overwrites in place.
|
|
2699
|
+
*/
|
|
2700
|
+
register(toolName, classification) {
|
|
2701
|
+
if (typeof toolName !== "string" || toolName.length === 0) {
|
|
2702
|
+
throw new Error("ClassificationRegistry.register: toolName must be a non-empty string");
|
|
2703
|
+
}
|
|
2704
|
+
const prev = this.byToolName.get(toolName);
|
|
2705
|
+
this.byToolName.set(toolName, classification);
|
|
2706
|
+
return prev;
|
|
2707
|
+
}
|
|
2708
|
+
/**
|
|
2709
|
+
* Bulk-register an array of classifications keyed by `${specId}.${operationId}`.
|
|
2710
|
+
* Returns the count of newly-added entries (existing entries are overwritten
|
|
2711
|
+
* but not counted toward `added`).
|
|
2712
|
+
*/
|
|
2713
|
+
registerAll(classifications) {
|
|
2714
|
+
let added = 0;
|
|
2715
|
+
let replaced = 0;
|
|
2716
|
+
for (const c of classifications) {
|
|
2717
|
+
const toolName = `${c.specId}.${c.operationId}`;
|
|
2718
|
+
const prev = this.byToolName.get(toolName);
|
|
2719
|
+
this.byToolName.set(toolName, c);
|
|
2720
|
+
if (prev) replaced++;
|
|
2721
|
+
else added++;
|
|
2722
|
+
}
|
|
2723
|
+
return { added, replaced };
|
|
2724
|
+
}
|
|
2725
|
+
/** Look up the classification for a tool. */
|
|
2726
|
+
lookup(toolName) {
|
|
2727
|
+
return this.byToolName.get(toolName);
|
|
2728
|
+
}
|
|
2729
|
+
/** Remove a single classification. Returns true if it was present. */
|
|
2730
|
+
unregister(toolName) {
|
|
2731
|
+
return this.byToolName.delete(toolName);
|
|
2732
|
+
}
|
|
2733
|
+
/** Drop every registration; used by the loader on full re-deploy. */
|
|
2734
|
+
clear() {
|
|
2735
|
+
this.byToolName.clear();
|
|
2736
|
+
}
|
|
2737
|
+
/** Current size — handy for tests and metrics. */
|
|
2738
|
+
size() {
|
|
2739
|
+
return this.byToolName.size;
|
|
2740
|
+
}
|
|
2741
|
+
/** Snapshot of every (toolName, classification) pair in insertion order. */
|
|
2742
|
+
getAll() {
|
|
2743
|
+
const out = [];
|
|
2744
|
+
for (const [toolName, classification] of this.byToolName.entries()) {
|
|
2745
|
+
out.push({ toolName, classification });
|
|
2746
|
+
}
|
|
2747
|
+
return out;
|
|
2748
|
+
}
|
|
2749
|
+
/**
|
|
2750
|
+
* Convenience: in one step, look up the classification for `toolName`
|
|
2751
|
+
* and build the resource-change notification it should produce after a
|
|
2752
|
+
* successful call with `args`. Returns `{ notification: null }` when the
|
|
2753
|
+
* tool has no classification (e.g. it's not an openapi-derived tool) or
|
|
2754
|
+
* when its classification has no `emit`.
|
|
2755
|
+
*
|
|
2756
|
+
* The dispatcher is expected to call this from a successful-call hook
|
|
2757
|
+
* and forward the resulting notification through whichever notification
|
|
2758
|
+
* channel the host provides.
|
|
2759
|
+
*/
|
|
2760
|
+
buildNotificationForCall(toolName, args) {
|
|
2761
|
+
const classification = this.byToolName.get(toolName);
|
|
2762
|
+
if (!classification) return { notification: null, reason: "no-emit" };
|
|
2763
|
+
return buildResourceChangeNotification(classification, args);
|
|
2764
|
+
}
|
|
2765
|
+
};
|
|
2069
2766
|
export {
|
|
2070
2767
|
BundlePinnedError,
|
|
2071
2768
|
BundlePushJwtVerifier,
|
|
2072
2769
|
BundleStore,
|
|
2770
|
+
ClassificationRegistry,
|
|
2073
2771
|
FilesystemSkillsSource,
|
|
2074
2772
|
Hs256AuditSigner,
|
|
2075
2773
|
MemoryAuditStore,
|
|
@@ -2089,15 +2787,25 @@ export {
|
|
|
2089
2787
|
StaticSource,
|
|
2090
2788
|
StorageAdapterAuditStore,
|
|
2091
2789
|
WebhookReplayGuard,
|
|
2790
|
+
applyClassificationOverrides,
|
|
2791
|
+
applyEnvironmentOverlay,
|
|
2792
|
+
buildKnownOps,
|
|
2793
|
+
buildResourceChangeNotification,
|
|
2092
2794
|
bundleDigest,
|
|
2093
2795
|
bundleSkillToActions,
|
|
2094
2796
|
bundleSourceSchema,
|
|
2095
2797
|
canonicalize,
|
|
2096
2798
|
canonicalizeRecordForSigning,
|
|
2799
|
+
classifyOne,
|
|
2800
|
+
classifyOperations,
|
|
2097
2801
|
createBundleSource,
|
|
2098
2802
|
crossValidate,
|
|
2803
|
+
crossValidateManifest,
|
|
2804
|
+
dedupeOpReferences,
|
|
2099
2805
|
defaultAuditSignatureVerifier,
|
|
2806
|
+
deployManifestSchema,
|
|
2100
2807
|
diffBundles,
|
|
2808
|
+
extractOpReferences,
|
|
2101
2809
|
formatDiffSummary,
|
|
2102
2810
|
hasSkillAuditFactory,
|
|
2103
2811
|
linkRecord,
|
|
@@ -2106,12 +2814,14 @@ export {
|
|
|
2106
2814
|
npmSourceSchema,
|
|
2107
2815
|
parseOverlay,
|
|
2108
2816
|
parseSkillFrontmatter,
|
|
2817
|
+
renderResourceUri,
|
|
2109
2818
|
resolveSkillLoadOrder,
|
|
2110
2819
|
resolvedBundleSchema,
|
|
2111
2820
|
saasSourceSchema,
|
|
2112
2821
|
setSkillAuditFactory,
|
|
2113
2822
|
signatureKeySchema,
|
|
2114
2823
|
staticSourceSchema,
|
|
2824
|
+
validateOpReferences,
|
|
2115
2825
|
verifyBundleSignature,
|
|
2116
2826
|
verifyChain
|
|
2117
2827
|
};
|