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/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 "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
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 getBuildPack(mod) {
24
- if (!mod) return undefined;
25
- if (typeof mod.buildPack === "function") return mod.buildPack;
26
- if (mod.default) {
27
- if (typeof mod.default === "function") return mod.default;
28
- if (typeof mod.default.buildPack === "function") return mod.default.buildPack;
29
- }
30
- if (typeof mod === "function") return mod;
31
- if (typeof mod.buildPack === "function") return mod.buildPack;
32
- 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 };
33
142
  }
34
143
 
35
- async function loadBuildPack() {
144
+ async function loadBuildExports() {
36
145
  const candidates = [
37
- path.resolve(__dirname, "../dist/index.js"),
38
- path.resolve(__dirname, "../dist/builder.js"),
39
- path.resolve(__dirname, "../dist/index.cjs"),
40
- 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'),
41
150
  ];
42
151
  for (const p of candidates) {
43
152
  const mod = await tryImport(p);
44
- const buildPack = getBuildPack(mod);
45
- if (buildPack) return buildPack;
153
+ const exports = pickBuildExports(mod);
154
+ if (exports) return exports;
46
155
  }
47
- throw new Error("Could not locate a buildPack function in dist/");
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('Input JSON must be an array of docs: [{ "text": "...", "id"?: "...", "heading"?: "..." }]');
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 !== "object") {
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 !== "string" || !doc.text.trim()) {
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 === "--embeddings") {
187
+ if (arg === '--embeddings') {
77
188
  flags.embeddingsPath = argv[++i];
78
189
  continue;
79
190
  }
80
- if (arg === "--model-id") {
191
+ if (arg === '--model-id') {
81
192
  flags.modelId = argv[++i];
82
193
  continue;
83
194
  }
84
- if (arg === "--help" || arg === "-h") {
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, "utf8"));
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("Embeddings must contain non-empty numeric vectors.");
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("Usage: knolo <input.json> [output.knolo] [--embeddings embeddings.json --model-id model-name]");
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 loadBuildPack();
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] || "knowledge.knolo";
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, "utf8");
343
+ const rawText = readFileSync(inFile, 'utf8');
150
344
  const parsed = JSON.parse(rawText);
151
345
  const docs = validateCliDocs(parsed);
152
346
 
153
- let options;
347
+ const options = {};
348
+
154
349
  if (flags.embeddingsPath || flags.modelId) {
155
350
  if (!flags.embeddingsPath || !flags.modelId) {
156
- throw new Error("Both --embeddings and --model-id are required when enabling semantic build output.");
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
- semantic: {
161
- enabled: true,
162
- modelId: flags.modelId,
163
- embeddings,
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}`);
@@ -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;