@botbotgo/agent-harness 0.0.18 → 0.0.20

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.
@@ -0,0 +1,143 @@
1
+ import path from "node:path";
2
+ import { z } from "zod";
3
+ import { TOOL_DEFINITION_MARKER, normalizeToolSchema } from "./tools.js";
4
+ const TOOL_MODULE_EXTENSIONS = new Set([".mjs", ".js", ".cjs"]);
5
+ const ANNOTATED_TOOL_EXPORT_PATTERN = /(?:\/\*\*?[\s\S]*?@tool[\s\S]*?\*\/\s*|\/\/[^\n]*@tool[^\n]*\n\s*)export\s+(?:async\s+)?(?:function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(|const\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:async\s*)?(?:function\b|\())/g;
6
+ function asRecord(value) {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value)
8
+ ? value
9
+ : undefined;
10
+ }
11
+ function isSchemaLike(value) {
12
+ return typeof value === "object" && value !== null && typeof value.parse === "function";
13
+ }
14
+ function isToolDefinitionObject(value) {
15
+ return typeof value === "object" && value !== null && value[TOOL_DEFINITION_MARKER] === true;
16
+ }
17
+ function buildSchemaFromShape(shape, description) {
18
+ const record = asRecord(shape);
19
+ if (!record) {
20
+ return null;
21
+ }
22
+ const schema = z.object(record);
23
+ return description ? schema.describe(description) : schema;
24
+ }
25
+ function readToolDescription(imported, implementationName, schema) {
26
+ const explicit = typeof imported[`${implementationName}Description`] === "string"
27
+ ? String(imported[`${implementationName}Description`])
28
+ : typeof imported.description === "string"
29
+ ? String(imported.description)
30
+ : undefined;
31
+ return explicit?.trim() || schema.description?.trim() || `Auto-discovered tool ${implementationName}`;
32
+ }
33
+ function resolveToolSchema(imported, implementationName, allowGenericExports) {
34
+ const namedSchema = imported[`${implementationName}Schema`];
35
+ if (isSchemaLike(namedSchema)) {
36
+ return namedSchema;
37
+ }
38
+ const namedShape = imported[`${implementationName}SchemaShape`];
39
+ const namedDescription = typeof imported[`${implementationName}Description`] === "string"
40
+ ? String(imported[`${implementationName}Description`])
41
+ : undefined;
42
+ const schemaFromNamedShape = buildSchemaFromShape(namedShape, namedDescription);
43
+ if (schemaFromNamedShape) {
44
+ return schemaFromNamedShape;
45
+ }
46
+ if (!allowGenericExports) {
47
+ return null;
48
+ }
49
+ const genericSchema = imported.schema;
50
+ if (isSchemaLike(genericSchema)) {
51
+ return genericSchema;
52
+ }
53
+ const genericShape = imported.schemaShape ?? imported.schema;
54
+ const genericDescription = typeof imported.description === "string" ? String(imported.description) : undefined;
55
+ return buildSchemaFromShape(genericShape, genericDescription);
56
+ }
57
+ function discoverExportedFunctionNames(imported) {
58
+ return Object.entries(imported)
59
+ .filter(([name, value]) => name !== "default" && typeof value === "function")
60
+ .map(([name]) => name)
61
+ .sort();
62
+ }
63
+ function discoverExportedToolObjectNames(imported) {
64
+ return Object.entries(imported)
65
+ .filter(([name, value]) => name !== "default" && isToolDefinitionObject(value))
66
+ .map(([name]) => name)
67
+ .sort();
68
+ }
69
+ function loadToolObjectDefinition(imported, exportName) {
70
+ const definition = imported[exportName];
71
+ if (!isToolDefinitionObject(definition)) {
72
+ return null;
73
+ }
74
+ return {
75
+ implementationName: definition.name?.trim() || exportName,
76
+ invoke: definition.invoke,
77
+ schema: normalizeToolSchema(definition.schema),
78
+ description: definition.description.trim(),
79
+ };
80
+ }
81
+ export function isSupportedToolModulePath(filePath) {
82
+ return TOOL_MODULE_EXTENSIONS.has(path.extname(filePath));
83
+ }
84
+ export function discoverAnnotatedFunctionNames(sourceText) {
85
+ const matches = sourceText.matchAll(ANNOTATED_TOOL_EXPORT_PATTERN);
86
+ return Array.from(matches, (match) => match[1] ?? match[2]).filter((name) => Boolean(name));
87
+ }
88
+ export function discoverToolModuleDefinitions(sourceText, imported) {
89
+ const objectDefinitions = discoverExportedToolObjectNames(imported)
90
+ .map((exportName) => loadToolObjectDefinition(imported, exportName))
91
+ .filter((definition) => Boolean(definition));
92
+ if (objectDefinitions.length > 0) {
93
+ return objectDefinitions;
94
+ }
95
+ const annotatedNames = new Set(discoverAnnotatedFunctionNames(sourceText));
96
+ const functionNames = discoverExportedFunctionNames(imported);
97
+ const allowGenericExports = functionNames.length === 1;
98
+ const discovered = [];
99
+ for (const implementationName of functionNames) {
100
+ const invoke = imported[implementationName];
101
+ if (typeof invoke !== "function") {
102
+ continue;
103
+ }
104
+ const schema = resolveToolSchema(imported, implementationName, allowGenericExports);
105
+ if (!schema && !annotatedNames.has(implementationName)) {
106
+ continue;
107
+ }
108
+ if (!schema) {
109
+ throw new Error(`Tool module must export ${implementationName}Schema, ${implementationName}SchemaShape, or generic schema/schemaShape metadata.`);
110
+ }
111
+ discovered.push({
112
+ implementationName,
113
+ invoke: invoke,
114
+ schema,
115
+ description: readToolDescription(imported, implementationName, schema),
116
+ });
117
+ }
118
+ return discovered;
119
+ }
120
+ export function loadToolModuleDefinition(imported, implementationName) {
121
+ for (const exportName of discoverExportedToolObjectNames(imported)) {
122
+ const loaded = loadToolObjectDefinition(imported, exportName);
123
+ if (loaded && loaded.implementationName === implementationName) {
124
+ return loaded;
125
+ }
126
+ }
127
+ const functionNames = discoverExportedFunctionNames(imported);
128
+ const allowGenericExports = functionNames.length === 1;
129
+ const invoke = imported[implementationName];
130
+ if (typeof invoke !== "function") {
131
+ throw new Error(`Tool module must export function ${implementationName}.`);
132
+ }
133
+ const schema = resolveToolSchema(imported, implementationName, allowGenericExports);
134
+ if (!schema) {
135
+ throw new Error(`Tool module must export ${implementationName}Schema, ${implementationName}SchemaShape, or generic schema/schemaShape metadata.`);
136
+ }
137
+ return {
138
+ implementationName,
139
+ invoke: invoke,
140
+ schema,
141
+ description: readToolDescription(imported, implementationName, schema),
142
+ };
143
+ }
@@ -0,0 +1,20 @@
1
+ import type { ZodRawShape, ZodTypeAny } from "zod";
2
+ export declare const TOOL_DEFINITION_MARKER = "__agent_harness_tool_definition__";
3
+ type SchemaInput = ZodRawShape | ZodTypeAny;
4
+ export type ToolDefinitionObject = {
5
+ name?: string;
6
+ description: string;
7
+ schema: SchemaInput;
8
+ invoke: (input: unknown, context?: Record<string, unknown>) => Promise<unknown> | unknown;
9
+ [TOOL_DEFINITION_MARKER]: true;
10
+ };
11
+ export declare function normalizeToolSchema(schema: SchemaInput): ZodTypeAny & {
12
+ shape?: ZodRawShape;
13
+ };
14
+ export declare function tool(definition: {
15
+ name?: string;
16
+ description: string;
17
+ schema: SchemaInput;
18
+ invoke: (input: unknown, context?: Record<string, unknown>) => Promise<unknown> | unknown;
19
+ }): ToolDefinitionObject;
20
+ export {};
package/dist/tools.js ADDED
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ export const TOOL_DEFINITION_MARKER = "__agent_harness_tool_definition__";
3
+ function isZodSchema(value) {
4
+ return typeof value === "object" && value !== null && typeof value.parse === "function";
5
+ }
6
+ export function normalizeToolSchema(schema) {
7
+ if (isZodSchema(schema)) {
8
+ return schema;
9
+ }
10
+ return z.object(schema);
11
+ }
12
+ export function tool(definition) {
13
+ return {
14
+ ...definition,
15
+ [TOOL_DEFINITION_MARKER]: true,
16
+ };
17
+ }
@@ -1,9 +1,10 @@
1
1
  import path from "node:path";
2
2
  import { createHash } from "node:crypto";
3
3
  import { ensureResourceSources, listResourceTools, listResourceToolsForSource } from "../resource/resource.js";
4
+ import { listRemoteMcpTools } from "../resource/resource-impl.js";
4
5
  import { ensureExternalResourceSource, isExternalSourceLocator } from "../resource/sources.js";
5
6
  import { loadWorkspaceObjects, readToolModuleItems } from "./object-loader.js";
6
- import { parseEmbeddingModelObject, parseModelObject, parseToolObject, parseVectorStoreObject, validateEmbeddingModelObject, validateModelObject, validateToolObject, validateVectorStoreObject, } from "./resource-compilers.js";
7
+ import { parseEmbeddingModelObject, parseMcpServerObject, parseModelObject, parseToolObject, parseVectorStoreObject, validateEmbeddingModelObject, validateMcpServerObject, validateModelObject, validateToolObject, validateVectorStoreObject, } from "./resource-compilers.js";
7
8
  import { validateAgent, validateTopology } from "./validate.js";
8
9
  import { compileBinding } from "./agent-binding-compiler.js";
9
10
  import { discoverSubagents, ensureDiscoverySources } from "./support/discovery.js";
@@ -11,6 +12,7 @@ import { collectAgentDiscoverySourceRefs, collectToolSourceRefs } from "./suppor
11
12
  import { resolveRefId } from "./support/workspace-ref-utils.js";
12
13
  function collectParsedResources(refs) {
13
14
  const embeddings = new Map();
15
+ const mcpServers = new Map();
14
16
  const models = new Map();
15
17
  const vectorStores = new Map();
16
18
  const tools = new Map();
@@ -22,6 +24,9 @@ function collectParsedResources(refs) {
22
24
  if (ref.startsWith("embedding-model/")) {
23
25
  embeddings.set(typed.id, parseEmbeddingModelObject(object));
24
26
  }
27
+ else if (ref.startsWith("mcp/")) {
28
+ mcpServers.set(typed.id, parseMcpServerObject(object));
29
+ }
25
30
  else if (ref.startsWith("vector-store/")) {
26
31
  vectorStores.set(typed.id, parseVectorStoreObject(object));
27
32
  }
@@ -32,7 +37,7 @@ function collectParsedResources(refs) {
32
37
  tools.set(typed.id, parseToolObject(object));
33
38
  }
34
39
  }
35
- return { embeddings, models, vectorStores, tools };
40
+ return { embeddings, mcpServers, models, vectorStores, tools };
36
41
  }
37
42
  async function hydrateResourceAndExternalTools(tools, toolSourceRefs, workspaceRoot) {
38
43
  for (const source of toolSourceRefs) {
@@ -87,13 +92,125 @@ async function hydrateResourceAndExternalTools(tools, toolSourceRefs, workspaceR
87
92
  });
88
93
  }
89
94
  }
90
- function validateWorkspaceResources(embeddings, models, vectorStores, tools, agents) {
95
+ function toMcpServerConfig(server) {
96
+ return {
97
+ transport: server.transport,
98
+ command: server.command,
99
+ args: server.args,
100
+ env: server.env,
101
+ cwd: server.cwd,
102
+ url: server.url,
103
+ token: server.token,
104
+ headers: server.headers,
105
+ };
106
+ }
107
+ function readStringArray(value) {
108
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
109
+ }
110
+ function compileRegexList(value, label) {
111
+ return readStringArray(value).map((pattern) => {
112
+ try {
113
+ return new RegExp(pattern);
114
+ }
115
+ catch (error) {
116
+ throw new Error(`${label} contains invalid regex ${JSON.stringify(pattern)}: ${error instanceof Error ? error.message : String(error)}`);
117
+ }
118
+ });
119
+ }
120
+ function compileMcpToolFilter(serverItem) {
121
+ return {
122
+ includeNames: new Set(readStringArray(serverItem.tools)),
123
+ excludeNames: new Set(readStringArray(serverItem.excludeTools)),
124
+ includePatterns: compileRegexList(serverItem.toolFilters ?? serverItem.toolFilter, "toolFilter"),
125
+ excludePatterns: compileRegexList(serverItem.excludeToolFilters ?? serverItem.excludeToolFilter, "excludeToolFilter"),
126
+ };
127
+ }
128
+ function shouldIncludeRemoteMcpTool(filter, toolName) {
129
+ const { includeNames, excludeNames, includePatterns, excludePatterns } = filter;
130
+ const includedByName = includeNames.size === 0 || includeNames.has(toolName);
131
+ const includedByPattern = includePatterns.length === 0 || includePatterns.some((pattern) => pattern.test(toolName));
132
+ if (!includedByName || !includedByPattern) {
133
+ return false;
134
+ }
135
+ if (excludeNames.has(toolName)) {
136
+ return false;
137
+ }
138
+ if (excludePatterns.some((pattern) => pattern.test(toolName))) {
139
+ return false;
140
+ }
141
+ return true;
142
+ }
143
+ async function hydrateAgentMcpTools(agents, mcpServers, tools) {
144
+ for (const agent of agents) {
145
+ const discoveredRefs = new Set(agent.toolRefs);
146
+ for (const item of agent.mcpServers ?? []) {
147
+ const name = typeof item.name === "string" && item.name.trim()
148
+ ? item.name.trim()
149
+ : typeof item.id === "string" && item.id.trim()
150
+ ? item.id.trim()
151
+ : "";
152
+ if (!name) {
153
+ throw new Error(`Agent ${agent.id} has an MCP server entry without a name`);
154
+ }
155
+ const serverId = `${agent.id}.${name}`;
156
+ const parsedServer = parseMcpServerObject({
157
+ id: serverId,
158
+ kind: "mcp",
159
+ sourcePath: agent.sourcePath,
160
+ value: item,
161
+ });
162
+ const filter = compileMcpToolFilter(item);
163
+ mcpServers.set(serverId, parsedServer);
164
+ const remoteTools = await listRemoteMcpTools(toMcpServerConfig(parsedServer));
165
+ for (const remoteTool of remoteTools) {
166
+ if (!shouldIncludeRemoteMcpTool(filter, remoteTool.name)) {
167
+ continue;
168
+ }
169
+ const toolId = `mcp.${agent.id}.${name}.${remoteTool.name}`;
170
+ tools.set(toolId, {
171
+ id: toolId,
172
+ type: "mcp",
173
+ name: remoteTool.name,
174
+ description: remoteTool.description ?? remoteTool.name,
175
+ config: {
176
+ mcp: {
177
+ serverRef: `mcp/${serverId}`,
178
+ },
179
+ },
180
+ mcpRef: remoteTool.name,
181
+ bundleRefs: [],
182
+ sourcePath: agent.sourcePath,
183
+ });
184
+ discoveredRefs.add(`tool/${toolId}`);
185
+ }
186
+ }
187
+ agent.toolRefs = Array.from(discoveredRefs);
188
+ }
189
+ }
190
+ function validateWorkspaceResources(embeddings, mcpServers, models, vectorStores, tools, agents) {
91
191
  embeddings.forEach((embedding) => validateEmbeddingModelObject(embedding));
192
+ mcpServers.forEach((server) => validateMcpServerObject(server));
92
193
  models.forEach((model) => validateModelObject(model, models));
93
194
  vectorStores.forEach((vectorStore) => validateVectorStoreObject(vectorStore));
94
195
  tools.forEach((tool) => validateToolObject(tool, tools));
95
196
  agents.forEach(validateAgent);
96
197
  validateTopology(agents);
198
+ tools.forEach((tool) => {
199
+ if (tool.type !== "mcp") {
200
+ return;
201
+ }
202
+ const mcpConfig = typeof tool.config?.mcp === "object" && tool.config.mcp
203
+ ? tool.config.mcp
204
+ : undefined;
205
+ const serverRef = typeof mcpConfig?.serverRef === "string" ? mcpConfig.serverRef : undefined;
206
+ if (!serverRef) {
207
+ throw new Error(`MCP tool ${tool.id} must define mcp.server or mcp.serverRef`);
208
+ }
209
+ const serverId = serverRef.startsWith("mcp/") ? serverRef.slice(4) : serverRef;
210
+ if (!mcpServers.has(serverId)) {
211
+ throw new Error(`MCP tool ${tool.id} references missing MCP server ${serverRef}`);
212
+ }
213
+ });
97
214
  }
98
215
  function compileBindings(workspaceRoot, refs, agentsList, models, tools) {
99
216
  const agents = new Map(agentsList.map((agent) => [agent.id, agent]));
@@ -112,17 +229,19 @@ export async function loadWorkspace(workspaceRoot, options = {}) {
112
229
  for (const agent of loaded.agents) {
113
230
  loaded.refs.set(`agent/${agent.id}`, agent);
114
231
  }
115
- const { embeddings, models, vectorStores, tools } = collectParsedResources(loaded.refs);
232
+ const { embeddings, mcpServers, models, vectorStores, tools } = collectParsedResources(loaded.refs);
233
+ await hydrateAgentMcpTools(loaded.agents, mcpServers, tools);
116
234
  const toolSourceRefs = collectToolSourceRefs(tools, loaded.agents, options);
117
235
  await ensureResourceSources(toolSourceRefs, workspaceRoot);
118
236
  await hydrateResourceAndExternalTools(tools, toolSourceRefs, workspaceRoot);
119
- validateWorkspaceResources(embeddings, models, vectorStores, tools, loaded.agents);
237
+ validateWorkspaceResources(embeddings, mcpServers, models, vectorStores, tools, loaded.agents);
120
238
  return {
121
239
  workspaceRoot,
122
240
  resourceSources: [...toolSourceRefs],
123
241
  builtinSources: [...toolSourceRefs],
124
242
  refs: loaded.refs,
125
243
  embeddings,
244
+ mcpServers,
126
245
  models,
127
246
  vectorStores,
128
247
  tools,
@@ -3,7 +3,9 @@ import { existsSync, statSync } from "node:fs";
3
3
  import { readdir, readFile } from "node:fs/promises";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import { parseAllDocuments } from "yaml";
6
+ import { resolveIsolatedResourceModulePath } from "../resource/isolation.js";
6
7
  import { resolveResourcePackageRoot } from "../resource/sources.js";
8
+ import { discoverToolModuleDefinitions, isSupportedToolModulePath } from "../tool-modules.js";
7
9
  import { fileExists, listFilesRecursive, readYamlOrJson } from "../utils/fs.js";
8
10
  const ROOT_AGENT_FILENAMES = [
9
11
  "agent.yaml",
@@ -15,6 +17,7 @@ const ROOT_AGENT_FILENAMES = [
15
17
  "research.yaml",
16
18
  "research.yml",
17
19
  ];
20
+ const MODEL_FILENAMES = ["models.yaml", "models.yml"];
18
21
  const LEGACY_GLOBAL_AGENT_FILENAMES = ["agent.yaml", "agent.yml"];
19
22
  const CONVENTIONAL_OBJECT_DIRECTORIES = ["tools"];
20
23
  function conventionalConfigRoot(root) {
@@ -69,6 +72,31 @@ function toArray(value) {
69
72
  function asObject(value) {
70
73
  return typeof value === "object" && value ? value : undefined;
71
74
  }
75
+ function normalizeNamedResourceSpec(document, kind) {
76
+ const typed = asObject(document);
77
+ const spec = typed?.spec;
78
+ if (!Array.isArray(spec)) {
79
+ return [];
80
+ }
81
+ const normalized = spec
82
+ .filter((item) => typeof item === "object" && item !== null && !Array.isArray(item))
83
+ .map((item) => {
84
+ const id = typeof item.id === "string" && item.id.trim()
85
+ ? item.id
86
+ : typeof item.name === "string" && item.name.trim()
87
+ ? item.name
88
+ : undefined;
89
+ if (!id) {
90
+ return null;
91
+ }
92
+ return {
93
+ ...item,
94
+ kind,
95
+ id,
96
+ };
97
+ });
98
+ return normalized.filter((item) => item !== null);
99
+ }
72
100
  function normalizeKind(kind) {
73
101
  switch (kind) {
74
102
  case "LangChainAgent":
@@ -109,6 +137,8 @@ function normalizeKind(kind) {
109
137
  return "runtime";
110
138
  case "Prompt":
111
139
  return "prompt";
140
+ case "McpServer":
141
+ return "mcp";
112
142
  default:
113
143
  return kind;
114
144
  }
@@ -153,7 +183,11 @@ function normalizeResourceItem(item) {
153
183
  }
154
184
  function readRefArray(items) {
155
185
  return toArray(items)
156
- .map((item) => (typeof item === "object" && item && "ref" in item ? item.ref : undefined))
186
+ .map((item) => typeof item === "string"
187
+ ? item
188
+ : typeof item === "object" && item && "ref" in item
189
+ ? item.ref
190
+ : undefined)
157
191
  .filter((item) => Boolean(item));
158
192
  }
159
193
  function readPathArray(items) {
@@ -180,6 +214,12 @@ function readMiddlewareArray(items) {
180
214
  .map((item) => ({ ...item }));
181
215
  return middleware.length > 0 ? middleware : undefined;
182
216
  }
217
+ function readObjectArray(items) {
218
+ const records = toArray(items)
219
+ .filter((item) => typeof item === "object" && item !== null && !Array.isArray(item))
220
+ .map((item) => ({ ...item }));
221
+ return records.length > 0 ? records : undefined;
222
+ }
183
223
  export function parseAgentItem(item, sourcePath) {
184
224
  const subagentRefs = readRefArray(item.subagents);
185
225
  const subagentPathRefs = readPathArray(item.subagents);
@@ -194,6 +234,7 @@ export function parseAgentItem(item, sourcePath) {
194
234
  modelRef: readSingleRef(item.modelRef) ?? "",
195
235
  runRoot: typeof item.runRoot === "string" ? item.runRoot : undefined,
196
236
  toolRefs: readRefArray(item.tools),
237
+ mcpServers: readObjectArray(item.mcpServers),
197
238
  skillPathRefs: readPathArray(item.skills),
198
239
  memorySources: readPathArray(item.memory),
199
240
  subagentRefs,
@@ -343,6 +384,23 @@ async function readNamedYamlItems(root, filenames) {
343
384
  }
344
385
  return records;
345
386
  }
387
+ async function readNamedModelItems(root) {
388
+ const records = [];
389
+ for (const filename of MODEL_FILENAMES) {
390
+ const filePath = path.join(root, filename);
391
+ if (!(await fileExists(filePath))) {
392
+ continue;
393
+ }
394
+ const parsedDocuments = parseAllDocuments(await readYamlOrJson(filePath));
395
+ for (const parsedDocument of parsedDocuments) {
396
+ const items = normalizeNamedResourceSpec(parsedDocument.toJSON(), "model");
397
+ for (const item of items) {
398
+ records.push({ item: normalizeYamlItem(item), sourcePath: filePath });
399
+ }
400
+ }
401
+ }
402
+ return records;
403
+ }
346
404
  function isAgentKind(kind) {
347
405
  return kind === "deepagent" || kind === "langchain-agent";
348
406
  }
@@ -358,44 +416,34 @@ async function readConfigAgentItems(configRoot) {
358
416
  return !(parentDir === configRoot && ROOT_AGENT_FILENAMES.includes(filename));
359
417
  });
360
418
  }
361
- function discoverAnnotatedFunctionNames(sourceText) {
362
- const matches = sourceText.matchAll(/@tool[\s\S]*?export\s+(?:async\s+)?function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g);
363
- return Array.from(matches, (match) => match[1]);
364
- }
365
419
  export async function readToolModuleItems(root) {
366
420
  if (!(await fileExists(root))) {
367
421
  return [];
368
422
  }
369
423
  const entries = await readdir(root, { withFileTypes: true });
370
424
  const files = entries
371
- .filter((entry) => entry.isFile() && entry.name.endsWith(".mjs"))
425
+ .filter((entry) => entry.isFile() && isSupportedToolModulePath(entry.name))
372
426
  .map((entry) => path.join(root, entry.name))
373
427
  .sort();
374
428
  const records = [];
375
429
  for (const filePath of files) {
376
430
  const sourceText = await readFile(filePath, "utf8");
377
- const functionNames = discoverAnnotatedFunctionNames(sourceText);
378
- if (functionNames.length === 0) {
431
+ const packageRoot = path.dirname(root);
432
+ const isolatedSourcePath = await resolveIsolatedResourceModulePath(packageRoot, filePath);
433
+ const imported = await import(pathToFileURL(isolatedSourcePath).href);
434
+ const definitions = discoverToolModuleDefinitions(sourceText, imported);
435
+ if (definitions.length === 0) {
379
436
  continue;
380
437
  }
381
- const imported = await import(pathToFileURL(filePath).href);
382
- for (const functionName of functionNames) {
383
- const invoke = imported[functionName];
384
- const schema = imported[`${functionName}Schema`];
385
- if (typeof invoke !== "function") {
386
- throw new Error(`Tool module ${filePath} must export function ${functionName}.`);
387
- }
388
- if (!schema?.parse) {
389
- throw new Error(`Tool module ${filePath} must export ${functionName}Schema as a Zod schema.`);
390
- }
438
+ for (const definition of definitions) {
391
439
  records.push({
392
440
  item: {
393
441
  kind: "tool",
394
- id: functionName,
442
+ id: definition.implementationName,
395
443
  type: "function",
396
- name: functionName,
397
- description: typeof schema.description === "string" ? schema.description : `Auto-discovered tool ${functionName}`,
398
- implementationName: functionName,
444
+ name: definition.implementationName,
445
+ description: definition.description,
446
+ implementationName: definition.implementationName,
399
447
  },
400
448
  sourcePath: filePath,
401
449
  });
@@ -480,6 +528,20 @@ export async function loadWorkspaceObjects(workspaceRoot, options = {}) {
480
528
  }
481
529
  }
482
530
  }
531
+ for (const modelRoot of Array.from(new Set([configRoot]))) {
532
+ for (const { item, sourcePath } of await readNamedModelItems(modelRoot)) {
533
+ const workspaceObject = parseWorkspaceObject(item, sourcePath);
534
+ if (!workspaceObject || workspaceObject.kind !== "model") {
535
+ continue;
536
+ }
537
+ const ref = `${workspaceObject.kind}/${workspaceObject.id}`;
538
+ const current = mergedObjects.get(ref);
539
+ mergedObjects.set(ref, {
540
+ item: current ? mergeValues(current.item, item) : item,
541
+ sourcePath,
542
+ });
543
+ }
544
+ }
483
545
  for (const { item, sourcePath } of await readConfigAgentItems(configRoot)) {
484
546
  const id = typeof item.id === "string" ? item.id : undefined;
485
547
  if (!id) {
@@ -496,7 +558,7 @@ export async function loadWorkspaceObjects(workspaceRoot, options = {}) {
496
558
  for (const objectRoot of conventionalDirectoryRoots(root, directory)) {
497
559
  for (const { item, sourcePath } of await readYamlItems(objectRoot, undefined, { recursive: true })) {
498
560
  const workspaceObject = parseWorkspaceObject(item, sourcePath);
499
- if (!workspaceObject) {
561
+ if (!workspaceObject || workspaceObject.kind === "tool") {
500
562
  continue;
501
563
  }
502
564
  const ref = `${workspaceObject.kind}/${workspaceObject.id}`;
@@ -526,7 +588,8 @@ export async function loadWorkspaceObjects(workspaceRoot, options = {}) {
526
588
  if (!workspaceObject) {
527
589
  continue;
528
590
  }
529
- if (isAgentKind(workspaceObject.kind)) {
591
+ if (isAgentKind(workspaceObject.kind) ||
592
+ workspaceObject.kind === "model") {
530
593
  continue;
531
594
  }
532
595
  const ref = `${workspaceObject.kind}/${workspaceObject.id}`;
@@ -546,6 +609,9 @@ export async function loadWorkspaceObjects(workspaceRoot, options = {}) {
546
609
  if (!workspaceObject) {
547
610
  continue;
548
611
  }
612
+ if (workspaceObject.kind === "tool" || workspaceObject.kind === "mcp" || workspaceObject.kind === "model") {
613
+ continue;
614
+ }
549
615
  const ref = `${workspaceObject.kind}/${workspaceObject.id}`;
550
616
  const current = mergedObjects.get(ref);
551
617
  mergedObjects.set(ref, {
@@ -1,10 +1,12 @@
1
- import type { CompiledEmbeddingModel, CompiledModel, CompiledVectorStore, ParsedEmbeddingModelObject, ParsedVectorStoreObject, CompiledTool, ParsedModelObject, ParsedToolObject, WorkspaceObject } from "../contracts/types.js";
1
+ import type { CompiledEmbeddingModel, ParsedMcpServerObject, CompiledModel, CompiledVectorStore, ParsedEmbeddingModelObject, ParsedVectorStoreObject, CompiledTool, ParsedModelObject, ParsedToolObject, WorkspaceObject } from "../contracts/types.js";
2
2
  export declare function parseModelObject(object: WorkspaceObject): ParsedModelObject;
3
3
  export declare function parseEmbeddingModelObject(object: WorkspaceObject): ParsedEmbeddingModelObject;
4
4
  export declare function parseVectorStoreObject(object: WorkspaceObject): ParsedVectorStoreObject;
5
+ export declare function parseMcpServerObject(object: WorkspaceObject): ParsedMcpServerObject;
5
6
  export declare function validateModelObject(model: ParsedModelObject, models: Map<string, ParsedModelObject>): void;
6
7
  export declare function validateEmbeddingModelObject(model: ParsedEmbeddingModelObject): void;
7
8
  export declare function validateVectorStoreObject(vectorStore: ParsedVectorStoreObject): void;
9
+ export declare function validateMcpServerObject(server: ParsedMcpServerObject): void;
8
10
  export declare function compileModel(model: ParsedModelObject): CompiledModel;
9
11
  export declare function compileVectorStore(vectorStore: ParsedVectorStoreObject): CompiledVectorStore;
10
12
  export declare function compileEmbeddingModel(model: ParsedEmbeddingModelObject): CompiledEmbeddingModel;