@botbotgo/agent-harness 0.0.18 → 0.0.19
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/README.md +86 -491
- package/dist/api.d.ts +3 -0
- package/dist/api.js +7 -0
- package/dist/config/agent-context.md +8 -0
- package/dist/config/orchestra.yaml +11 -8
- package/dist/config/{runtime.yaml → workspace.yaml} +23 -0
- package/dist/contracts/types.d.ts +14 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/mcp.d.ts +12 -0
- package/dist/mcp.js +112 -0
- package/dist/resource/isolation.d.ts +2 -0
- package/dist/resource/isolation.js +79 -0
- package/dist/resource/resource-impl.d.ts +18 -0
- package/dist/resource/resource-impl.js +179 -11
- package/dist/resource/sources.d.ts +3 -0
- package/dist/resource/sources.js +105 -25
- package/dist/runtime/checkpoint-maintenance.d.ts +36 -0
- package/dist/runtime/checkpoint-maintenance.js +223 -0
- package/dist/runtime/harness.d.ts +10 -1
- package/dist/runtime/harness.js +38 -2
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/sqlite-maintained-checkpoint-saver.d.ts +9 -0
- package/dist/runtime/sqlite-maintained-checkpoint-saver.js +39 -0
- package/dist/runtime/support/runtime-factories.js +3 -1
- package/dist/tool-modules.d.ts +17 -0
- package/dist/tool-modules.js +143 -0
- package/dist/tools.d.ts +20 -0
- package/dist/tools.js +17 -0
- package/dist/workspace/compile.js +124 -5
- package/dist/workspace/object-loader.js +90 -24
- package/dist/workspace/resource-compilers.d.ts +3 -1
- package/dist/workspace/resource-compilers.js +72 -5
- package/package.json +10 -3
|
@@ -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
|
+
}
|
package/dist/tools.d.ts
ADDED
|
@@ -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
|
|
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) =>
|
|
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
|
|
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
|
|
378
|
-
|
|
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
|
|
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:
|
|
442
|
+
id: definition.implementationName,
|
|
395
443
|
type: "function",
|
|
396
|
-
name:
|
|
397
|
-
description:
|
|
398
|
-
implementationName:
|
|
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;
|