knolo-core 0.3.0 → 3.1.1
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 +16 -2
- package/README.md +376 -46
- package/bin/knolo.mjs +256 -45
- package/dist/agent.d.ts +53 -0
- package/dist/agent.js +175 -0
- package/dist/builder.d.ts +2 -0
- package/dist/builder.js +28 -6
- package/dist/index.d.ts +14 -2
- package/dist/index.js +9 -2
- package/dist/pack.d.ts +2 -0
- package/dist/pack.js +22 -3
- package/dist/query.d.ts +1 -0
- package/dist/query.js +43 -1
- package/dist/router.d.ts +28 -0
- package/dist/router.js +74 -0
- package/dist/routing_profile.d.ts +19 -0
- package/dist/routing_profile.js +102 -0
- package/dist/tool_gate.d.ts +3 -0
- package/dist/tool_gate.js +8 -0
- package/dist/tool_parse.d.ts +2 -0
- package/dist/tool_parse.js +102 -0
- package/dist/tools.d.ts +27 -0
- package/dist/tools.js +34 -0
- package/dist/trace.d.ts +45 -0
- package/dist/trace.js +12 -0
- package/package.json +4 -2
package/bin/knolo.mjs
CHANGED
|
@@ -1,14 +1,111 @@
|
|
|
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
110
|
try {
|
|
14
111
|
const url = pathToFileURL(filePath).href;
|
|
@@ -20,43 +117,57 @@ async function tryImport(filePath) {
|
|
|
20
117
|
return null;
|
|
21
118
|
}
|
|
22
119
|
|
|
23
|
-
function
|
|
24
|
-
if (!mod) return
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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 };
|
|
33
142
|
}
|
|
34
143
|
|
|
35
|
-
async function
|
|
144
|
+
async function loadBuildExports() {
|
|
36
145
|
const candidates = [
|
|
37
|
-
path.resolve(__dirname,
|
|
38
|
-
path.resolve(__dirname,
|
|
39
|
-
path.resolve(__dirname,
|
|
40
|
-
path.resolve(__dirname,
|
|
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'),
|
|
41
150
|
];
|
|
42
151
|
for (const p of candidates) {
|
|
43
152
|
const mod = await tryImport(p);
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
153
|
+
const exports = pickBuildExports(mod);
|
|
154
|
+
if (exports) return exports;
|
|
46
155
|
}
|
|
47
|
-
throw new Error(
|
|
156
|
+
throw new Error('Could not locate a buildPack function in dist/');
|
|
48
157
|
}
|
|
49
158
|
|
|
50
159
|
function validateCliDocs(raw) {
|
|
51
160
|
if (!Array.isArray(raw)) {
|
|
52
|
-
throw new Error(
|
|
161
|
+
throw new Error(
|
|
162
|
+
'Input JSON must be an array of docs: [{ "text": "...", "id"?: "...", "heading"?: "..." }]'
|
|
163
|
+
);
|
|
53
164
|
}
|
|
54
165
|
for (let i = 0; i < raw.length; i++) {
|
|
55
166
|
const doc = raw[i];
|
|
56
|
-
if (!doc || typeof doc !==
|
|
167
|
+
if (!doc || typeof doc !== 'object') {
|
|
57
168
|
throw new Error(`Invalid doc at index ${i}: expected an object.`);
|
|
58
169
|
}
|
|
59
|
-
if (typeof doc.text !==
|
|
170
|
+
if (typeof doc.text !== 'string' || !doc.text.trim()) {
|
|
60
171
|
throw new Error(`Invalid doc at index ${i}: "text" must be a non-empty string.`);
|
|
61
172
|
}
|
|
62
173
|
}
|
|
@@ -65,23 +176,27 @@ function validateCliDocs(raw) {
|
|
|
65
176
|
|
|
66
177
|
function parseArgs(argv) {
|
|
67
178
|
const positional = [];
|
|
68
|
-
const flags = { embeddingsPath: undefined, modelId: undefined };
|
|
179
|
+
const flags = { embeddingsPath: undefined, modelId: undefined, agentsDir: undefined };
|
|
69
180
|
|
|
70
181
|
for (let i = 0; i < argv.length; i++) {
|
|
71
182
|
const arg = argv[i];
|
|
72
|
-
if (!arg.startsWith(
|
|
183
|
+
if (!arg.startsWith('--')) {
|
|
73
184
|
positional.push(arg);
|
|
74
185
|
continue;
|
|
75
186
|
}
|
|
76
|
-
if (arg ===
|
|
187
|
+
if (arg === '--embeddings') {
|
|
77
188
|
flags.embeddingsPath = argv[++i];
|
|
78
189
|
continue;
|
|
79
190
|
}
|
|
80
|
-
if (arg ===
|
|
191
|
+
if (arg === '--model-id') {
|
|
81
192
|
flags.modelId = argv[++i];
|
|
82
193
|
continue;
|
|
83
194
|
}
|
|
84
|
-
if (arg ===
|
|
195
|
+
if (arg === '--agents') {
|
|
196
|
+
flags.agentsDir = argv[++i];
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (arg === '--help' || arg === '-h') {
|
|
85
200
|
flags.help = true;
|
|
86
201
|
continue;
|
|
87
202
|
}
|
|
@@ -90,8 +205,85 @@ function parseArgs(argv) {
|
|
|
90
205
|
return { positional, flags };
|
|
91
206
|
}
|
|
92
207
|
|
|
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
|
+
|
|
93
285
|
function loadEmbeddingsFromJson(filePath, expectedCount) {
|
|
94
|
-
const parsed = JSON.parse(readFileSync(filePath,
|
|
286
|
+
const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
95
287
|
const vectors = Array.isArray(parsed?.embeddings) ? parsed.embeddings : parsed;
|
|
96
288
|
if (!Array.isArray(vectors)) {
|
|
97
289
|
throw new Error('Embeddings JSON must be either an array of vectors or { "embeddings": [...] }.');
|
|
@@ -102,7 +294,7 @@ function loadEmbeddingsFromJson(filePath, expectedCount) {
|
|
|
102
294
|
|
|
103
295
|
const first = vectors[0];
|
|
104
296
|
if (!Array.isArray(first) || first.length === 0) {
|
|
105
|
-
throw new Error(
|
|
297
|
+
throw new Error('Embeddings must contain non-empty numeric vectors.');
|
|
106
298
|
}
|
|
107
299
|
const dims = first.length;
|
|
108
300
|
|
|
@@ -126,10 +318,12 @@ function loadEmbeddingsFromJson(filePath, expectedCount) {
|
|
|
126
318
|
}
|
|
127
319
|
|
|
128
320
|
function printUsage() {
|
|
129
|
-
console.log(
|
|
321
|
+
console.log(
|
|
322
|
+
'Usage: knolo <input.json> [output.knolo] [--agents ./agents] [--embeddings embeddings.json --model-id model-name]'
|
|
323
|
+
);
|
|
130
324
|
}
|
|
131
325
|
|
|
132
|
-
const buildPack = await
|
|
326
|
+
const { buildPack, validateAgentDefinition, validateAgentRegistry } = await loadBuildExports();
|
|
133
327
|
const { positional, flags } = parseArgs(process.argv.slice(2));
|
|
134
328
|
|
|
135
329
|
if (flags.help) {
|
|
@@ -138,7 +332,7 @@ if (flags.help) {
|
|
|
138
332
|
}
|
|
139
333
|
|
|
140
334
|
const inFile = positional[0];
|
|
141
|
-
const outFile = positional[1] ||
|
|
335
|
+
const outFile = positional[1] || 'knowledge.knolo';
|
|
142
336
|
|
|
143
337
|
if (!inFile) {
|
|
144
338
|
printUsage();
|
|
@@ -146,26 +340,43 @@ if (!inFile) {
|
|
|
146
340
|
}
|
|
147
341
|
|
|
148
342
|
try {
|
|
149
|
-
const rawText = readFileSync(inFile,
|
|
343
|
+
const rawText = readFileSync(inFile, 'utf8');
|
|
150
344
|
const parsed = JSON.parse(rawText);
|
|
151
345
|
const docs = validateCliDocs(parsed);
|
|
152
346
|
|
|
153
|
-
|
|
347
|
+
const options = {};
|
|
348
|
+
|
|
154
349
|
if (flags.embeddingsPath || flags.modelId) {
|
|
155
350
|
if (!flags.embeddingsPath || !flags.modelId) {
|
|
156
|
-
throw new Error(
|
|
351
|
+
throw new Error('Both --embeddings and --model-id are required when enabling semantic build output.');
|
|
157
352
|
}
|
|
158
353
|
const embeddings = loadEmbeddingsFromJson(flags.embeddingsPath, docs.length);
|
|
159
|
-
options = {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
quantization: { type: "int8_l2norm", perVectorScale: true },
|
|
165
|
-
},
|
|
354
|
+
options.semantic = {
|
|
355
|
+
enabled: true,
|
|
356
|
+
modelId: flags.modelId,
|
|
357
|
+
embeddings,
|
|
358
|
+
quantization: { type: 'int8_l2norm', perVectorScale: true },
|
|
166
359
|
};
|
|
167
360
|
}
|
|
168
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
|
+
|
|
169
380
|
const bytes = await buildPack(docs, options);
|
|
170
381
|
writeFileSync(outFile, Buffer.from(bytes));
|
|
171
382
|
console.log(`wrote ${outFile}`);
|
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,3 +1,4 @@
|
|
|
1
|
+
import type { AgentDefinitionV1, AgentRegistry } from './agent.js';
|
|
1
2
|
export type BuildInputDoc = {
|
|
2
3
|
id?: string;
|
|
3
4
|
heading?: string;
|
|
@@ -5,6 +6,7 @@ export type BuildInputDoc = {
|
|
|
5
6
|
text: string;
|
|
6
7
|
};
|
|
7
8
|
export type BuildPackOptions = {
|
|
9
|
+
agents?: AgentRegistry | AgentDefinitionV1[];
|
|
8
10
|
semantic?: {
|
|
9
11
|
enabled: boolean;
|
|
10
12
|
modelId: string;
|