@botbotgo/agent-harness 0.0.22 → 0.0.24

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,158 @@
1
+ import path from "node:path";
2
+ import { createHash } from "node:crypto";
3
+ import { listRemoteMcpTools } from "../resource/resource-impl.js";
4
+ import { ensureExternalResourceSource, isExternalSourceLocator } from "../resource/sources.js";
5
+ import { listResourceTools, listResourceToolsForSource } from "../resource/resource.js";
6
+ import { readToolModuleItems } from "./object-loader.js";
7
+ import { parseMcpServerObject, parseToolObject } from "./resource-compilers.js";
8
+ function toMcpServerConfig(server) {
9
+ return {
10
+ transport: server.transport,
11
+ command: server.command,
12
+ args: server.args,
13
+ env: server.env,
14
+ cwd: server.cwd,
15
+ url: server.url,
16
+ token: server.token,
17
+ headers: server.headers,
18
+ };
19
+ }
20
+ function readStringArray(value) {
21
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
22
+ }
23
+ function compileRegexList(value, label) {
24
+ return readStringArray(value).map((pattern) => {
25
+ try {
26
+ return new RegExp(pattern);
27
+ }
28
+ catch (error) {
29
+ throw new Error(`${label} contains invalid regex ${JSON.stringify(pattern)}: ${error instanceof Error ? error.message : String(error)}`);
30
+ }
31
+ });
32
+ }
33
+ function compileMcpToolFilter(serverItem) {
34
+ return {
35
+ includeNames: new Set(readStringArray(serverItem.tools)),
36
+ excludeNames: new Set(readStringArray(serverItem.excludeTools)),
37
+ includePatterns: compileRegexList(serverItem.toolFilters ?? serverItem.toolFilter, "toolFilter"),
38
+ excludePatterns: compileRegexList(serverItem.excludeToolFilters ?? serverItem.excludeToolFilter, "excludeToolFilter"),
39
+ };
40
+ }
41
+ function shouldIncludeRemoteMcpTool(filter, toolName) {
42
+ const { includeNames, excludeNames, includePatterns, excludePatterns } = filter;
43
+ const includedByName = includeNames.size === 0 || includeNames.has(toolName);
44
+ const includedByPattern = includePatterns.length === 0 || includePatterns.some((pattern) => pattern.test(toolName));
45
+ if (!includedByName || !includedByPattern) {
46
+ return false;
47
+ }
48
+ if (excludeNames.has(toolName)) {
49
+ return false;
50
+ }
51
+ if (excludePatterns.some((pattern) => pattern.test(toolName))) {
52
+ return false;
53
+ }
54
+ return true;
55
+ }
56
+ async function hydrateExternalToolSource(tools, source, workspaceRoot) {
57
+ const externalRoot = await ensureExternalResourceSource(source, workspaceRoot);
58
+ const discoveredToolRefs = [];
59
+ const sourcePrefix = `external.${createHash("sha256").update(source).digest("hex").slice(0, 12)}`;
60
+ for (const { item, sourcePath } of await readToolModuleItems(path.join(externalRoot, "tools"))) {
61
+ const toolId = typeof item.id === "string" ? item.id : undefined;
62
+ if (!toolId) {
63
+ continue;
64
+ }
65
+ const namespacedId = `${sourcePrefix}.${toolId}`;
66
+ const parsed = parseToolObject({
67
+ id: namespacedId,
68
+ kind: "tool",
69
+ sourcePath,
70
+ value: {
71
+ ...item,
72
+ id: namespacedId,
73
+ },
74
+ });
75
+ tools.set(parsed.id, parsed);
76
+ discoveredToolRefs.push(`tool/${parsed.id}`);
77
+ }
78
+ const sourceTools = await listResourceToolsForSource(source, workspaceRoot);
79
+ const bundleRefs = [...sourceTools.map((tool) => tool.toolPath), ...discoveredToolRefs];
80
+ if (bundleRefs.length > 0) {
81
+ tools.set(source, {
82
+ id: source,
83
+ type: "bundle",
84
+ name: source,
85
+ description: `External tool resource loaded from ${source}`,
86
+ bundleRefs,
87
+ sourcePath: source,
88
+ });
89
+ }
90
+ }
91
+ export async function hydrateResourceAndExternalTools(tools, toolSourceRefs, workspaceRoot) {
92
+ for (const source of toolSourceRefs) {
93
+ if (isExternalSourceLocator(source)) {
94
+ await hydrateExternalToolSource(tools, source, workspaceRoot);
95
+ }
96
+ }
97
+ for (const resourceTool of await listResourceTools(toolSourceRefs, workspaceRoot)) {
98
+ const existing = tools.get(resourceTool.toolPath);
99
+ tools.set(resourceTool.toolPath, {
100
+ id: resourceTool.toolPath,
101
+ type: existing?.type ?? "backend",
102
+ name: existing?.name || resourceTool.name,
103
+ description: existing?.description || resourceTool.description,
104
+ config: existing?.config,
105
+ backendOperation: existing?.backendOperation ?? resourceTool.backendOperation,
106
+ bundleRefs: existing?.bundleRefs ?? [],
107
+ hitl: existing?.hitl ?? resourceTool.hitl,
108
+ sourcePath: existing?.sourcePath ?? resourceTool.toolPath,
109
+ });
110
+ }
111
+ }
112
+ export async function hydrateAgentMcpTools(agents, mcpServers, tools) {
113
+ for (const agent of agents) {
114
+ const discoveredRefs = new Set(agent.toolRefs);
115
+ for (const item of agent.mcpServers ?? []) {
116
+ const name = typeof item.name === "string" && item.name.trim()
117
+ ? item.name.trim()
118
+ : typeof item.id === "string" && item.id.trim()
119
+ ? item.id.trim()
120
+ : "";
121
+ if (!name) {
122
+ throw new Error(`Agent ${agent.id} has an MCP server entry without a name`);
123
+ }
124
+ const serverId = `${agent.id}.${name}`;
125
+ const parsedServer = parseMcpServerObject({
126
+ id: serverId,
127
+ kind: "mcp",
128
+ sourcePath: agent.sourcePath,
129
+ value: item,
130
+ });
131
+ const filter = compileMcpToolFilter(item);
132
+ mcpServers.set(serverId, parsedServer);
133
+ const remoteTools = await listRemoteMcpTools(toMcpServerConfig(parsedServer));
134
+ for (const remoteTool of remoteTools) {
135
+ if (!shouldIncludeRemoteMcpTool(filter, remoteTool.name)) {
136
+ continue;
137
+ }
138
+ const toolId = `mcp.${agent.id}.${name}.${remoteTool.name}`;
139
+ tools.set(toolId, {
140
+ id: toolId,
141
+ type: "mcp",
142
+ name: remoteTool.name,
143
+ description: remoteTool.description ?? remoteTool.name,
144
+ config: {
145
+ mcp: {
146
+ serverRef: `mcp/${serverId}`,
147
+ },
148
+ },
149
+ mcpRef: remoteTool.name,
150
+ bundleRefs: [],
151
+ sourcePath: agent.sourcePath,
152
+ });
153
+ discoveredRefs.add(`tool/${toolId}`);
154
+ }
155
+ }
156
+ agent.toolRefs = Array.from(discoveredRefs);
157
+ }
158
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "description": "Agent Harness framework package",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",
@@ -1,44 +0,0 @@
1
- # agent-harness feature: schema version for this declarative config object.
2
- apiVersion: agent-harness/v1alpha1
3
- # agent-harness feature: object type for a named model preset.
4
- # Available options today: `Model`.
5
- # The harness resolves this object into LangChain model construction parameters.
6
- kind: Model
7
- metadata:
8
- # agent-harness feature: stable model object id used by refs such as `model/default`.
9
- name: default
10
- spec:
11
- # ====================
12
- # LangChain v1 Features
13
- # ====================
14
- # LangChain aligned feature: provider family or integration namespace.
15
- # Common options in this harness today include:
16
- # - `ollama`
17
- # - `openai`
18
- # - `openai-compatible`
19
- # - `anthropic`
20
- # - `google` / `google-genai` / `gemini`
21
- # The runtime adapter uses this to select the concrete LangChain chat model implementation.
22
- provider: ollama
23
- # LangChain aligned feature: concrete model identifier passed to the selected provider integration.
24
- # Example values depend on `provider`, such as `gpt-oss:latest` for `ollama`.
25
- model: gpt-oss:latest
26
- init:
27
- # LangChain aligned feature: provider-specific initialization options.
28
- # Available keys are provider-specific; common examples include `baseUrl`, `temperature`, and auth/client settings.
29
- # `baseUrl` configures the Ollama-compatible endpoint used by the model client.
30
- # For `openai-compatible`, `baseUrl` is normalized into the ChatOpenAI `configuration.baseURL` field.
31
- baseUrl: https://ollama-rtx-4070.easynet.world/
32
- # LangChain aligned feature: provider/model initialization option controlling sampling temperature.
33
- temperature: 0.2
34
- # ===================
35
- # DeepAgents Features
36
- # ===================
37
- # DeepAgents uses the same model object shape indirectly through `createDeepAgent({ model })`.
38
- # There is no separate DeepAgents-only field here; DeepAgent bindings consume the same compiled model.
39
-
40
- # ======================
41
- # agent-harness Features
42
- # ======================
43
- # This object is packaged and referenced through `modelRef` fields in harness config, but the actual
44
- # model arguments above map directly onto the upstream LangChain model layer.
File without changes