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/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 "node:fs";
5
- import path from "node:path";
6
- import { fileURLToPath, pathToFileURL } from "node:url";
7
- import { createRequire } from "node:module";
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 getBuildPack(mod) {
26
- if (!mod) return undefined;
27
- // Named export (ESM)
28
- if (typeof mod.buildPack === "function") return mod.buildPack;
29
- // CJS default export object: { buildPack } or function
30
- if (mod.default) {
31
- if (typeof mod.default === "function") return mod.default;
32
- if (typeof mod.default.buildPack === "function") return mod.default.buildPack;
33
- }
34
- // Some CJS setups export { buildPack } directly
35
- if (typeof mod === "function") return mod;
36
- if (typeof mod.buildPack === "function") return mod.buildPack;
37
- return undefined;
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 loadBuildPack() {
144
+ async function loadBuildExports() {
41
145
  const candidates = [
42
- path.resolve(__dirname, "../dist/index.js"),
43
- path.resolve(__dirname, "../dist/builder.js"),
44
- // Also try .cjs just in case someone built CJS
45
- path.resolve(__dirname, "../dist/index.cjs"),
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 buildPack = getBuildPack(mod);
51
- if (buildPack) return buildPack;
153
+ const exports = pickBuildExports(mod);
154
+ if (exports) return exports;
52
155
  }
53
- throw new Error("Could not locate a buildPack function in dist/");
156
+ throw new Error('Could not locate a buildPack function in dist/');
54
157
  }
55
158
 
56
- const buildPack = await loadBuildPack();
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
- const inFile = process.argv[2];
59
- const outFile = process.argv[3] || "knowledge.knolo";
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
- console.log("Usage: knolo <input.json> [output.knolo]");
338
+ printUsage();
63
339
  process.exit(1);
64
340
  }
65
341
 
66
- const docs = JSON.parse(readFileSync(inFile, "utf8"));
67
- const bytes = await buildPack(docs);
68
- writeFileSync(outFile, Buffer.from(bytes));
69
- console.log(`wrote ${outFile}`);
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
+ }
@@ -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 declare function buildPack(docs: BuildInputDoc[]): Promise<Uint8Array>;
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>;