knolo-core 0.2.3 → 3.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/DOCS.md +242 -14
- package/README.md +342 -150
- package/bin/knolo.mjs +354 -36
- package/dist/agent.d.ts +53 -0
- package/dist/agent.js +175 -0
- package/dist/builder.d.ts +15 -1
- package/dist/builder.js +128 -14
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -2
- package/dist/indexer.d.ts +2 -1
- package/dist/indexer.js +3 -2
- package/dist/pack.d.ts +14 -0
- package/dist/pack.js +96 -4
- package/dist/patch.d.ts +1 -8
- package/dist/patch.js +2 -17
- package/dist/query.d.ts +29 -0
- package/dist/query.js +324 -18
- package/dist/rank.d.ts +1 -1
- package/dist/rank.js +5 -4
- package/dist/semantic.d.ts +7 -0
- package/dist/semantic.js +98 -0
- package/dist/tokenize.js +1 -1
- package/package.json +5 -2
package/bin/knolo.mjs
CHANGED
|
@@ -1,69 +1,387 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Robust CLI that works with ESM or CJS builds and odd resolution cases.
|
|
3
3
|
|
|
4
|
-
import { readFileSync, writeFileSync } from
|
|
5
|
-
import path from
|
|
6
|
-
import { fileURLToPath, pathToFileURL } from
|
|
7
|
-
import { createRequire } from
|
|
4
|
+
import { readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
8
|
|
|
9
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const require = createRequire(import.meta.url);
|
|
11
11
|
|
|
12
|
+
function parseScalar(value) {
|
|
13
|
+
if (value === 'true') return true;
|
|
14
|
+
if (value === 'false') return false;
|
|
15
|
+
if (value === 'null') return null;
|
|
16
|
+
if (/^-?\d+(?:\.\d+)?$/.test(value)) return Number(value);
|
|
17
|
+
const quoted = value.match(/^("|')(.*)\1$/);
|
|
18
|
+
if (quoted) return quoted[2];
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseSimpleYaml(content) {
|
|
23
|
+
const lines = content
|
|
24
|
+
.split(/\n/)
|
|
25
|
+
.map((line) => line.replace(/\r$/, ''))
|
|
26
|
+
.map((line) => line.replace(/\t/g, ' ').replace(/\s+#.*$/, ''));
|
|
27
|
+
|
|
28
|
+
const root = {};
|
|
29
|
+
const stack = [{ indent: -1, value: root }];
|
|
30
|
+
|
|
31
|
+
const nextMeaningfulLine = (from) => {
|
|
32
|
+
for (let i = from + 1; i < lines.length; i++) {
|
|
33
|
+
if (lines[i].trim()) return lines[i].trim();
|
|
34
|
+
}
|
|
35
|
+
return '';
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < lines.length; i++) {
|
|
39
|
+
const raw = lines[i];
|
|
40
|
+
if (!raw.trim()) continue;
|
|
41
|
+
|
|
42
|
+
const indent = raw.match(/^\s*/)[0].length;
|
|
43
|
+
const line = raw.trim();
|
|
44
|
+
|
|
45
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
46
|
+
stack.pop();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const parent = stack[stack.length - 1].value;
|
|
50
|
+
|
|
51
|
+
if (line.startsWith('- ')) {
|
|
52
|
+
if (!Array.isArray(parent)) {
|
|
53
|
+
throw new Error('YAML array item found under non-array parent.');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const body = line.slice(2).trim();
|
|
57
|
+
const pair = body.match(/^([^:]+):(.*)$/);
|
|
58
|
+
if (!pair) {
|
|
59
|
+
parent.push(parseScalar(body));
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const obj = {};
|
|
64
|
+
const key = pair[1].trim();
|
|
65
|
+
const rhs = pair[2].trim();
|
|
66
|
+
if (rhs) {
|
|
67
|
+
obj[key] = parseScalar(rhs);
|
|
68
|
+
} else {
|
|
69
|
+
const next = nextMeaningfulLine(i);
|
|
70
|
+
obj[key] = next.startsWith('- ') ? [] : {};
|
|
71
|
+
stack.push({ indent, value: obj[key] });
|
|
72
|
+
}
|
|
73
|
+
parent.push(obj);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const pair = line.match(/^([^:]+):(.*)$/);
|
|
78
|
+
if (!pair) {
|
|
79
|
+
throw new Error(`Unsupported YAML line: ${line}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const key = pair[1].trim();
|
|
83
|
+
const rhs = pair[2].trim();
|
|
84
|
+
if (Array.isArray(parent)) {
|
|
85
|
+
const obj = {};
|
|
86
|
+
parent.push(obj);
|
|
87
|
+
if (rhs) {
|
|
88
|
+
obj[key] = parseScalar(rhs);
|
|
89
|
+
} else {
|
|
90
|
+
const next = nextMeaningfulLine(i);
|
|
91
|
+
obj[key] = next.startsWith('- ') ? [] : {};
|
|
92
|
+
stack.push({ indent, value: obj[key] });
|
|
93
|
+
}
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (rhs) {
|
|
98
|
+
parent[key] = parseScalar(rhs);
|
|
99
|
+
} else {
|
|
100
|
+
const next = nextMeaningfulLine(i);
|
|
101
|
+
parent[key] = next.startsWith('- ') ? [] : {};
|
|
102
|
+
stack.push({ indent, value: parent[key] });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return root;
|
|
107
|
+
}
|
|
108
|
+
|
|
12
109
|
async function tryImport(filePath) {
|
|
13
|
-
// 1) ESM via file URL
|
|
14
110
|
try {
|
|
15
111
|
const url = pathToFileURL(filePath).href;
|
|
16
112
|
return await import(url);
|
|
17
113
|
} catch (_) {}
|
|
18
|
-
// 2) CJS via require
|
|
19
114
|
try {
|
|
20
115
|
return require(filePath);
|
|
21
116
|
} catch (_) {}
|
|
22
117
|
return null;
|
|
23
118
|
}
|
|
24
119
|
|
|
25
|
-
function
|
|
26
|
-
if (!mod) return
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
120
|
+
function pickBuildExports(mod) {
|
|
121
|
+
if (!mod) return null;
|
|
122
|
+
const root = mod.default && typeof mod.default === 'object' ? mod.default : mod;
|
|
123
|
+
const buildPack =
|
|
124
|
+
typeof mod.buildPack === 'function'
|
|
125
|
+
? mod.buildPack
|
|
126
|
+
: typeof root.buildPack === 'function'
|
|
127
|
+
? root.buildPack
|
|
128
|
+
: typeof root === 'function'
|
|
129
|
+
? root
|
|
130
|
+
: undefined;
|
|
131
|
+
const validateAgentDefinition =
|
|
132
|
+
typeof mod.validateAgentDefinition === 'function'
|
|
133
|
+
? mod.validateAgentDefinition
|
|
134
|
+
: root.validateAgentDefinition;
|
|
135
|
+
const validateAgentRegistry =
|
|
136
|
+
typeof mod.validateAgentRegistry === 'function'
|
|
137
|
+
? mod.validateAgentRegistry
|
|
138
|
+
: root.validateAgentRegistry;
|
|
139
|
+
|
|
140
|
+
if (!buildPack) return null;
|
|
141
|
+
return { buildPack, validateAgentDefinition, validateAgentRegistry };
|
|
38
142
|
}
|
|
39
143
|
|
|
40
|
-
async function
|
|
144
|
+
async function loadBuildExports() {
|
|
41
145
|
const candidates = [
|
|
42
|
-
path.resolve(__dirname,
|
|
43
|
-
path.resolve(__dirname,
|
|
44
|
-
|
|
45
|
-
path.resolve(__dirname,
|
|
46
|
-
path.resolve(__dirname, "../dist/builder.cjs"),
|
|
146
|
+
path.resolve(__dirname, '../dist/index.js'),
|
|
147
|
+
path.resolve(__dirname, '../dist/builder.js'),
|
|
148
|
+
path.resolve(__dirname, '../dist/index.cjs'),
|
|
149
|
+
path.resolve(__dirname, '../dist/builder.cjs'),
|
|
47
150
|
];
|
|
48
151
|
for (const p of candidates) {
|
|
49
152
|
const mod = await tryImport(p);
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
153
|
+
const exports = pickBuildExports(mod);
|
|
154
|
+
if (exports) return exports;
|
|
52
155
|
}
|
|
53
|
-
throw new Error(
|
|
156
|
+
throw new Error('Could not locate a buildPack function in dist/');
|
|
54
157
|
}
|
|
55
158
|
|
|
56
|
-
|
|
159
|
+
function validateCliDocs(raw) {
|
|
160
|
+
if (!Array.isArray(raw)) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
'Input JSON must be an array of docs: [{ "text": "...", "id"?: "...", "heading"?: "..." }]'
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
for (let i = 0; i < raw.length; i++) {
|
|
166
|
+
const doc = raw[i];
|
|
167
|
+
if (!doc || typeof doc !== 'object') {
|
|
168
|
+
throw new Error(`Invalid doc at index ${i}: expected an object.`);
|
|
169
|
+
}
|
|
170
|
+
if (typeof doc.text !== 'string' || !doc.text.trim()) {
|
|
171
|
+
throw new Error(`Invalid doc at index ${i}: "text" must be a non-empty string.`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return raw;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function parseArgs(argv) {
|
|
178
|
+
const positional = [];
|
|
179
|
+
const flags = { embeddingsPath: undefined, modelId: undefined, agentsDir: undefined };
|
|
180
|
+
|
|
181
|
+
for (let i = 0; i < argv.length; i++) {
|
|
182
|
+
const arg = argv[i];
|
|
183
|
+
if (!arg.startsWith('--')) {
|
|
184
|
+
positional.push(arg);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (arg === '--embeddings') {
|
|
188
|
+
flags.embeddingsPath = argv[++i];
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (arg === '--model-id') {
|
|
192
|
+
flags.modelId = argv[++i];
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (arg === '--agents') {
|
|
196
|
+
flags.agentsDir = argv[++i];
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (arg === '--help' || arg === '-h') {
|
|
200
|
+
flags.help = true;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
throw new Error(`Unknown flag: ${arg}`);
|
|
204
|
+
}
|
|
205
|
+
return { positional, flags };
|
|
206
|
+
}
|
|
57
207
|
|
|
58
|
-
|
|
59
|
-
const
|
|
208
|
+
function parseAgentFileContent(content, filePath) {
|
|
209
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
210
|
+
if (ext === '.json') return JSON.parse(content);
|
|
211
|
+
if (ext === '.yaml' || ext === '.yml') return parseSimpleYaml(content);
|
|
212
|
+
throw new Error(`Unsupported agent file extension: ${filePath}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function normalizeAgentFromFile(parsed, filePath) {
|
|
216
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
217
|
+
throw new Error(`Invalid agent definition in ${filePath}: expected object.`);
|
|
218
|
+
}
|
|
219
|
+
if ('agent' in parsed && parsed.agent && typeof parsed.agent === 'object') {
|
|
220
|
+
return parsed.agent;
|
|
221
|
+
}
|
|
222
|
+
return parsed;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function loadAgentsFromDir(agentsDir, validators = {}) {
|
|
226
|
+
const { validateAgentDefinition, validateAgentRegistry } = validators;
|
|
227
|
+
const dirPath = path.resolve(agentsDir);
|
|
228
|
+
let entries;
|
|
229
|
+
try {
|
|
230
|
+
entries = readdirSync(dirPath, { withFileTypes: true });
|
|
231
|
+
} catch (err) {
|
|
232
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
233
|
+
throw new Error(`Unable to read agents directory ${agentsDir}: ${message}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const files = entries
|
|
237
|
+
.filter((entry) => entry.isFile())
|
|
238
|
+
.map((entry) => entry.name)
|
|
239
|
+
.filter((name) => ['.json', '.yaml', '.yml'].includes(path.extname(name).toLowerCase()))
|
|
240
|
+
.sort((a, b) => a.localeCompare(b));
|
|
241
|
+
|
|
242
|
+
const loaded = [];
|
|
243
|
+
for (const file of files) {
|
|
244
|
+
const fullPath = path.join(dirPath, file);
|
|
245
|
+
try {
|
|
246
|
+
const content = readFileSync(fullPath, 'utf8');
|
|
247
|
+
const parsed = parseAgentFileContent(content, fullPath);
|
|
248
|
+
const agent = normalizeAgentFromFile(parsed, fullPath);
|
|
249
|
+
if (typeof validateAgentDefinition === 'function') {
|
|
250
|
+
validateAgentDefinition(agent);
|
|
251
|
+
}
|
|
252
|
+
loaded.push({ file, agent });
|
|
253
|
+
} catch (err) {
|
|
254
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
255
|
+
throw new Error(`Failed to load agent file ${fullPath}: ${message}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const duplicateById = new Map();
|
|
260
|
+
for (const item of loaded) {
|
|
261
|
+
const key = String(item.agent?.id ?? '');
|
|
262
|
+
if (!duplicateById.has(key)) duplicateById.set(key, []);
|
|
263
|
+
duplicateById.get(key).push(item.file);
|
|
264
|
+
}
|
|
265
|
+
for (const [id, fileNames] of duplicateById.entries()) {
|
|
266
|
+
if (fileNames.length > 1) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
`Duplicate agent id "${id}" found in files: ${fileNames.sort((a, b) => a.localeCompare(b)).join(', ')}`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const agents = loaded
|
|
274
|
+
.map((item) => item.agent)
|
|
275
|
+
.sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
276
|
+
const registry = { version: 1, agents };
|
|
277
|
+
|
|
278
|
+
if (typeof validateAgentRegistry === 'function') {
|
|
279
|
+
validateAgentRegistry(registry);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return registry;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function loadEmbeddingsFromJson(filePath, expectedCount) {
|
|
286
|
+
const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
287
|
+
const vectors = Array.isArray(parsed?.embeddings) ? parsed.embeddings : parsed;
|
|
288
|
+
if (!Array.isArray(vectors)) {
|
|
289
|
+
throw new Error('Embeddings JSON must be either an array of vectors or { "embeddings": [...] }.');
|
|
290
|
+
}
|
|
291
|
+
if (vectors.length !== expectedCount) {
|
|
292
|
+
throw new Error(`Embeddings length mismatch: expected ${expectedCount}, got ${vectors.length}.`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const first = vectors[0];
|
|
296
|
+
if (!Array.isArray(first) || first.length === 0) {
|
|
297
|
+
throw new Error('Embeddings must contain non-empty numeric vectors.');
|
|
298
|
+
}
|
|
299
|
+
const dims = first.length;
|
|
300
|
+
|
|
301
|
+
return vectors.map((entry, i) => {
|
|
302
|
+
if (!Array.isArray(entry)) {
|
|
303
|
+
throw new Error(`Embeddings[${i}] must be an array of numbers.`);
|
|
304
|
+
}
|
|
305
|
+
if (entry.length !== dims) {
|
|
306
|
+
throw new Error(`Embeddings[${i}] has dims ${entry.length}, expected ${dims}.`);
|
|
307
|
+
}
|
|
308
|
+
const vec = new Float32Array(dims);
|
|
309
|
+
for (let d = 0; d < dims; d++) {
|
|
310
|
+
const value = entry[d];
|
|
311
|
+
if (!Number.isFinite(value)) {
|
|
312
|
+
throw new Error(`Embeddings[${i}][${d}] must be a finite number.`);
|
|
313
|
+
}
|
|
314
|
+
vec[d] = value;
|
|
315
|
+
}
|
|
316
|
+
return vec;
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function printUsage() {
|
|
321
|
+
console.log(
|
|
322
|
+
'Usage: knolo <input.json> [output.knolo] [--agents ./agents] [--embeddings embeddings.json --model-id model-name]'
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const { buildPack, validateAgentDefinition, validateAgentRegistry } = await loadBuildExports();
|
|
327
|
+
const { positional, flags } = parseArgs(process.argv.slice(2));
|
|
328
|
+
|
|
329
|
+
if (flags.help) {
|
|
330
|
+
printUsage();
|
|
331
|
+
process.exit(0);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const inFile = positional[0];
|
|
335
|
+
const outFile = positional[1] || 'knowledge.knolo';
|
|
60
336
|
|
|
61
337
|
if (!inFile) {
|
|
62
|
-
|
|
338
|
+
printUsage();
|
|
63
339
|
process.exit(1);
|
|
64
340
|
}
|
|
65
341
|
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
342
|
+
try {
|
|
343
|
+
const rawText = readFileSync(inFile, 'utf8');
|
|
344
|
+
const parsed = JSON.parse(rawText);
|
|
345
|
+
const docs = validateCliDocs(parsed);
|
|
346
|
+
|
|
347
|
+
const options = {};
|
|
348
|
+
|
|
349
|
+
if (flags.embeddingsPath || flags.modelId) {
|
|
350
|
+
if (!flags.embeddingsPath || !flags.modelId) {
|
|
351
|
+
throw new Error('Both --embeddings and --model-id are required when enabling semantic build output.');
|
|
352
|
+
}
|
|
353
|
+
const embeddings = loadEmbeddingsFromJson(flags.embeddingsPath, docs.length);
|
|
354
|
+
options.semantic = {
|
|
355
|
+
enabled: true,
|
|
356
|
+
modelId: flags.modelId,
|
|
357
|
+
embeddings,
|
|
358
|
+
quantization: { type: 'int8_l2norm', perVectorScale: true },
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (flags.agentsDir) {
|
|
363
|
+
let dirStats;
|
|
364
|
+
try {
|
|
365
|
+
dirStats = statSync(flags.agentsDir);
|
|
366
|
+
} catch (err) {
|
|
367
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
368
|
+
throw new Error(`Unable to access --agents path ${flags.agentsDir}: ${message}`);
|
|
369
|
+
}
|
|
370
|
+
if (!dirStats.isDirectory()) {
|
|
371
|
+
throw new Error(`--agents path must be a directory: ${flags.agentsDir}`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
options.agents = loadAgentsFromDir(flags.agentsDir, {
|
|
375
|
+
validateAgentDefinition,
|
|
376
|
+
validateAgentRegistry,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const bytes = await buildPack(docs, options);
|
|
381
|
+
writeFileSync(outFile, Buffer.from(bytes));
|
|
382
|
+
console.log(`wrote ${outFile}`);
|
|
383
|
+
} catch (err) {
|
|
384
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
385
|
+
console.error(`knolo: ${message}`);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Pack } from './pack.js';
|
|
2
|
+
import type { QueryOptions } from './query.js';
|
|
3
|
+
export type AgentPromptTemplate = string[] | {
|
|
4
|
+
format: 'markdown';
|
|
5
|
+
template: string;
|
|
6
|
+
};
|
|
7
|
+
export type AgentToolPolicy = {
|
|
8
|
+
mode: 'allow' | 'deny';
|
|
9
|
+
tools: string[];
|
|
10
|
+
};
|
|
11
|
+
export type AgentRetrievalDefaults = {
|
|
12
|
+
namespace: string[];
|
|
13
|
+
topK?: number;
|
|
14
|
+
queryExpansion?: QueryOptions['queryExpansion'];
|
|
15
|
+
semantic?: Omit<NonNullable<QueryOptions['semantic']>, 'queryEmbedding' | 'enabled' | 'force'> & {
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
};
|
|
18
|
+
minScore?: number;
|
|
19
|
+
requirePhrases?: string[];
|
|
20
|
+
source?: string[];
|
|
21
|
+
};
|
|
22
|
+
export type AgentDefinitionV1 = {
|
|
23
|
+
id: string;
|
|
24
|
+
version: 1;
|
|
25
|
+
name?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
systemPrompt: AgentPromptTemplate;
|
|
28
|
+
retrievalDefaults: AgentRetrievalDefaults;
|
|
29
|
+
toolPolicy?: AgentToolPolicy;
|
|
30
|
+
metadata?: Record<string, string | number | boolean | null>;
|
|
31
|
+
};
|
|
32
|
+
export type AgentRegistry = {
|
|
33
|
+
version: 1;
|
|
34
|
+
agents: AgentDefinitionV1[];
|
|
35
|
+
};
|
|
36
|
+
export type ResolveAgentInput = {
|
|
37
|
+
agentId: string;
|
|
38
|
+
query?: QueryOptions;
|
|
39
|
+
patch?: Record<string, string | number | boolean>;
|
|
40
|
+
};
|
|
41
|
+
export type ResolvedAgent = {
|
|
42
|
+
agent: AgentDefinitionV1;
|
|
43
|
+
systemPrompt: string;
|
|
44
|
+
retrievalOptions: QueryOptions;
|
|
45
|
+
};
|
|
46
|
+
export declare function validateAgentRegistry(reg: AgentRegistry): void;
|
|
47
|
+
export declare function validateAgentDefinition(agent: AgentDefinitionV1): void;
|
|
48
|
+
export declare function listAgents(pack: Pack): string[];
|
|
49
|
+
export declare function getAgent(pack: Pack, agentId: string): AgentDefinitionV1 | undefined;
|
|
50
|
+
export declare function buildSystemPrompt(agent: AgentDefinitionV1, patch?: Record<string, string | number | boolean>): string;
|
|
51
|
+
export declare function resolveAgent(pack: Pack, input: ResolveAgentInput): ResolvedAgent;
|
|
52
|
+
export declare function isToolAllowed(agent: AgentDefinitionV1, toolId: string): boolean;
|
|
53
|
+
export declare function assertToolAllowed(agent: AgentDefinitionV1, toolId: string): void;
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { validateQueryOptions } from './query.js';
|
|
2
|
+
export function validateAgentRegistry(reg) {
|
|
3
|
+
if (!reg || typeof reg !== 'object') {
|
|
4
|
+
throw new Error('agent registry must be an object.');
|
|
5
|
+
}
|
|
6
|
+
if (reg.version !== 1) {
|
|
7
|
+
throw new Error('agent registry version must be 1.');
|
|
8
|
+
}
|
|
9
|
+
if (!Array.isArray(reg.agents)) {
|
|
10
|
+
throw new Error('agent registry agents must be an array.');
|
|
11
|
+
}
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
for (const agent of reg.agents) {
|
|
14
|
+
validateAgentDefinition(agent);
|
|
15
|
+
if (seen.has(agent.id)) {
|
|
16
|
+
throw new Error(`agent id must be unique: ${agent.id}`);
|
|
17
|
+
}
|
|
18
|
+
seen.add(agent.id);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function validateAgentDefinition(agent) {
|
|
22
|
+
if (!agent || typeof agent !== 'object') {
|
|
23
|
+
throw new Error('agent definition must be an object.');
|
|
24
|
+
}
|
|
25
|
+
if (typeof agent.id !== 'string' || !agent.id.trim()) {
|
|
26
|
+
throw new Error('agent id must be a non-empty string.');
|
|
27
|
+
}
|
|
28
|
+
if (!/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(agent.id)) {
|
|
29
|
+
throw new Error(`agent id must be slug-like: ${agent.id}`);
|
|
30
|
+
}
|
|
31
|
+
if (agent.version !== 1) {
|
|
32
|
+
throw new Error(`agent ${agent.id} version must be 1.`);
|
|
33
|
+
}
|
|
34
|
+
validateSystemPrompt(agent);
|
|
35
|
+
const defaults = agent.retrievalDefaults;
|
|
36
|
+
if (!defaults || typeof defaults !== 'object') {
|
|
37
|
+
throw new Error(`agent ${agent.id} retrievalDefaults must be an object.`);
|
|
38
|
+
}
|
|
39
|
+
if (!Array.isArray(defaults.namespace) ||
|
|
40
|
+
defaults.namespace.length === 0 ||
|
|
41
|
+
defaults.namespace.some((ns) => typeof ns !== 'string' || !ns.trim())) {
|
|
42
|
+
throw new Error(`agent ${agent.id} retrievalDefaults.namespace must be a non-empty string array.`);
|
|
43
|
+
}
|
|
44
|
+
if (defaults.topK !== undefined &&
|
|
45
|
+
(!Number.isInteger(defaults.topK) || defaults.topK < 1)) {
|
|
46
|
+
throw new Error(`agent ${agent.id} retrievalDefaults.topK must be a positive integer.`);
|
|
47
|
+
}
|
|
48
|
+
if (agent.toolPolicy) {
|
|
49
|
+
const { mode, tools } = agent.toolPolicy;
|
|
50
|
+
if (mode !== 'allow' && mode !== 'deny') {
|
|
51
|
+
throw new Error(`agent ${agent.id} toolPolicy.mode must be "allow" or "deny".`);
|
|
52
|
+
}
|
|
53
|
+
if (!Array.isArray(tools) ||
|
|
54
|
+
tools.some((tool) => typeof tool !== 'string' || !tool.trim())) {
|
|
55
|
+
throw new Error(`agent ${agent.id} toolPolicy.tools must be a string array.`);
|
|
56
|
+
}
|
|
57
|
+
if (new Set(tools).size !== tools.length) {
|
|
58
|
+
throw new Error(`agent ${agent.id} toolPolicy.tools must contain unique values.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const syntheticOpts = {
|
|
62
|
+
namespace: defaults.namespace,
|
|
63
|
+
topK: defaults.topK,
|
|
64
|
+
queryExpansion: defaults.queryExpansion,
|
|
65
|
+
semantic: defaults.semantic,
|
|
66
|
+
minScore: defaults.minScore,
|
|
67
|
+
requirePhrases: defaults.requirePhrases,
|
|
68
|
+
source: defaults.source,
|
|
69
|
+
};
|
|
70
|
+
validateQueryOptions(syntheticOpts);
|
|
71
|
+
}
|
|
72
|
+
export function listAgents(pack) {
|
|
73
|
+
const reg = pack.meta.agents;
|
|
74
|
+
if (!reg?.agents?.length)
|
|
75
|
+
return [];
|
|
76
|
+
return reg.agents.map((agent) => agent.id);
|
|
77
|
+
}
|
|
78
|
+
export function getAgent(pack, agentId) {
|
|
79
|
+
return pack.meta.agents?.agents.find((agent) => agent.id === agentId);
|
|
80
|
+
}
|
|
81
|
+
export function buildSystemPrompt(agent, patch = {}) {
|
|
82
|
+
const template = agent.systemPrompt;
|
|
83
|
+
if (Array.isArray(template)) {
|
|
84
|
+
return template.join('\n');
|
|
85
|
+
}
|
|
86
|
+
const source = template.template;
|
|
87
|
+
const placeholders = Array.from(source.matchAll(/\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/g)).map((m) => m[1]);
|
|
88
|
+
for (const key of placeholders) {
|
|
89
|
+
if (!(key in patch)) {
|
|
90
|
+
throw new Error(`agent ${agent.id} system prompt missing patch value for placeholder: ${key}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return source.replace(/\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/g, (_match, key) => String(patch[key]));
|
|
94
|
+
}
|
|
95
|
+
export function resolveAgent(pack, input) {
|
|
96
|
+
const agent = getAgent(pack, input.agentId);
|
|
97
|
+
if (!agent) {
|
|
98
|
+
throw new Error(`agent not found: ${input.agentId}`);
|
|
99
|
+
}
|
|
100
|
+
const defaults = {
|
|
101
|
+
namespace: agent.retrievalDefaults.namespace,
|
|
102
|
+
topK: agent.retrievalDefaults.topK,
|
|
103
|
+
queryExpansion: agent.retrievalDefaults.queryExpansion,
|
|
104
|
+
semantic: agent.retrievalDefaults.semantic,
|
|
105
|
+
minScore: agent.retrievalDefaults.minScore,
|
|
106
|
+
requirePhrases: agent.retrievalDefaults.requirePhrases,
|
|
107
|
+
source: agent.retrievalDefaults.source,
|
|
108
|
+
};
|
|
109
|
+
const caller = input.query ?? {};
|
|
110
|
+
const retrievalOptions = {
|
|
111
|
+
...defaults,
|
|
112
|
+
...caller,
|
|
113
|
+
namespace: defaults.namespace,
|
|
114
|
+
queryExpansion: {
|
|
115
|
+
...(defaults.queryExpansion ?? {}),
|
|
116
|
+
...(caller.queryExpansion ?? {}),
|
|
117
|
+
},
|
|
118
|
+
semantic: {
|
|
119
|
+
...(defaults.semantic ?? {}),
|
|
120
|
+
...(caller.semantic ?? {}),
|
|
121
|
+
blend: {
|
|
122
|
+
...(defaults.semantic?.blend ?? {}),
|
|
123
|
+
...(caller.semantic?.blend ?? {}),
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
if (!defaults.queryExpansion && !caller.queryExpansion)
|
|
128
|
+
delete retrievalOptions.queryExpansion;
|
|
129
|
+
if (!defaults.semantic && !caller.semantic)
|
|
130
|
+
delete retrievalOptions.semantic;
|
|
131
|
+
if (retrievalOptions.semantic &&
|
|
132
|
+
!defaults.semantic?.blend &&
|
|
133
|
+
!caller.semantic?.blend) {
|
|
134
|
+
delete retrievalOptions.semantic.blend;
|
|
135
|
+
}
|
|
136
|
+
validateQueryOptions(retrievalOptions);
|
|
137
|
+
return {
|
|
138
|
+
agent,
|
|
139
|
+
systemPrompt: buildSystemPrompt(agent, input.patch),
|
|
140
|
+
retrievalOptions,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
export function isToolAllowed(agent, toolId) {
|
|
144
|
+
const policy = agent.toolPolicy;
|
|
145
|
+
if (!policy)
|
|
146
|
+
return true;
|
|
147
|
+
const hasTool = policy.tools.includes(toolId);
|
|
148
|
+
if (policy.mode === 'allow') {
|
|
149
|
+
return hasTool;
|
|
150
|
+
}
|
|
151
|
+
return !hasTool;
|
|
152
|
+
}
|
|
153
|
+
export function assertToolAllowed(agent, toolId) {
|
|
154
|
+
if (!isToolAllowed(agent, toolId)) {
|
|
155
|
+
throw new Error(`agent ${agent.id} does not allow tool: ${toolId}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function validateSystemPrompt(agent) {
|
|
159
|
+
const prompt = agent.systemPrompt;
|
|
160
|
+
if (Array.isArray(prompt)) {
|
|
161
|
+
if (!prompt.length || prompt.some((line) => typeof line !== 'string')) {
|
|
162
|
+
throw new Error(`agent ${agent.id} systemPrompt must be a non-empty string array.`);
|
|
163
|
+
}
|
|
164
|
+
if (!prompt.join('').trim()) {
|
|
165
|
+
throw new Error(`agent ${agent.id} systemPrompt must not be empty.`);
|
|
166
|
+
}
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (!prompt ||
|
|
170
|
+
prompt.format !== 'markdown' ||
|
|
171
|
+
typeof prompt.template !== 'string' ||
|
|
172
|
+
!prompt.template.trim()) {
|
|
173
|
+
throw new Error(`agent ${agent.id} systemPrompt markdown template must be a non-empty string.`);
|
|
174
|
+
}
|
|
175
|
+
}
|
package/dist/builder.d.ts
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
|
+
import type { AgentDefinitionV1, AgentRegistry } from './agent.js';
|
|
1
2
|
export type BuildInputDoc = {
|
|
2
3
|
id?: string;
|
|
3
4
|
heading?: string;
|
|
5
|
+
namespace?: string;
|
|
4
6
|
text: string;
|
|
5
7
|
};
|
|
6
|
-
export
|
|
8
|
+
export type BuildPackOptions = {
|
|
9
|
+
agents?: AgentRegistry | AgentDefinitionV1[];
|
|
10
|
+
semantic?: {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
modelId: string;
|
|
13
|
+
embeddings: Float32Array[];
|
|
14
|
+
quantization?: {
|
|
15
|
+
type: 'int8_l2norm';
|
|
16
|
+
perVectorScale?: true;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export declare function buildPack(docs: BuildInputDoc[], opts?: BuildPackOptions): Promise<Uint8Array>;
|