@botbotgo/agent-harness 0.0.100 → 0.0.102

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.
Files changed (40) hide show
  1. package/dist/package-version.d.ts +1 -1
  2. package/dist/package-version.js +1 -1
  3. package/dist/persistence/sqlite-run-context-store.d.ts +22 -0
  4. package/dist/persistence/sqlite-run-context-store.js +64 -0
  5. package/dist/persistence/sqlite-run-queue-store.d.ts +41 -0
  6. package/dist/persistence/sqlite-run-queue-store.js +120 -0
  7. package/dist/persistence/sqlite-store.d.ts +2 -2
  8. package/dist/persistence/sqlite-store.js +31 -117
  9. package/dist/resource/mcp-tool-support.d.ts +21 -0
  10. package/dist/resource/mcp-tool-support.js +173 -0
  11. package/dist/resource/resource-impl.d.ts +1 -18
  12. package/dist/resource/resource-impl.js +79 -240
  13. package/dist/runtime/adapter/invoke-runtime.d.ts +22 -0
  14. package/dist/runtime/adapter/invoke-runtime.js +18 -0
  15. package/dist/runtime/adapter/stream-runtime.d.ts +46 -0
  16. package/dist/runtime/adapter/stream-runtime.js +93 -0
  17. package/dist/runtime/agent-runtime-adapter.d.ts +1 -12
  18. package/dist/runtime/agent-runtime-adapter.js +122 -312
  19. package/dist/runtime/harness/run/recovery.d.ts +42 -0
  20. package/dist/runtime/harness/run/recovery.js +139 -0
  21. package/dist/runtime/harness/run/run-operations.d.ts +50 -0
  22. package/dist/runtime/harness/run/run-operations.js +113 -0
  23. package/dist/runtime/harness/run/run-slot-acquisition.d.ts +64 -0
  24. package/dist/runtime/harness/run/run-slot-acquisition.js +157 -0
  25. package/dist/runtime/harness/run/stream-run.d.ts +53 -0
  26. package/dist/runtime/harness/run/stream-run.js +304 -0
  27. package/dist/runtime/harness.d.ts +2 -17
  28. package/dist/runtime/harness.js +157 -773
  29. package/dist/runtime/support/runtime-factories.js +2 -2
  30. package/dist/workspace/object-loader.d.ts +1 -8
  31. package/dist/workspace/object-loader.js +43 -275
  32. package/dist/workspace/yaml-object-reader.d.ts +15 -0
  33. package/dist/workspace/yaml-object-reader.js +202 -0
  34. package/package.json +1 -1
  35. package/dist/runtime/checkpoint-maintenance.d.ts +0 -1
  36. package/dist/runtime/checkpoint-maintenance.js +0 -1
  37. package/dist/runtime/file-checkpoint-saver.d.ts +0 -1
  38. package/dist/runtime/file-checkpoint-saver.js +0 -1
  39. package/dist/runtime/sqlite-maintained-checkpoint-saver.d.ts +0 -1
  40. package/dist/runtime/sqlite-maintained-checkpoint-saver.js +0 -1
@@ -0,0 +1,173 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
3
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5
+ import { WebSocketClientTransport } from "@modelcontextprotocol/sdk/client/websocket.js";
6
+ import { AGENT_HARNESS_VERSION } from "../package-version.js";
7
+ import { createRuntimeEnv } from "../runtime/support/runtime-env.js";
8
+ const mcpClientCache = new Map();
9
+ function readStringRecord(value) {
10
+ if (typeof value !== "object" || !value) {
11
+ return undefined;
12
+ }
13
+ const entries = Object.entries(value).filter((entry) => typeof entry[1] === "string");
14
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
15
+ }
16
+ function normalizeMcpTransport(value) {
17
+ if (value === "stdio" || value === "http" || value === "sse" || value === "websocket") {
18
+ return value;
19
+ }
20
+ return undefined;
21
+ }
22
+ export function readMcpServerConfig(workspace, tool) {
23
+ const mcpConfig = typeof tool.config?.mcp === "object" && tool.config?.mcp
24
+ ? tool.config.mcp
25
+ : undefined;
26
+ const serverRef = typeof mcpConfig?.serverRef === "string" ? mcpConfig.serverRef : undefined;
27
+ if (serverRef) {
28
+ const serverId = serverRef.startsWith("mcp/") ? serverRef.slice(4) : serverRef;
29
+ const server = workspace.mcpServers.get(serverId);
30
+ if (!server) {
31
+ throw new Error(`MCP tool ${tool.id} references missing MCP server ${serverRef}`);
32
+ }
33
+ return {
34
+ transport: server.transport,
35
+ command: server.command,
36
+ args: server.args,
37
+ env: server.env,
38
+ cwd: server.cwd,
39
+ url: server.url,
40
+ token: server.token,
41
+ headers: server.headers,
42
+ };
43
+ }
44
+ const config = typeof tool.config === "object" && tool.config
45
+ ? tool.config
46
+ : undefined;
47
+ const mcpServer = typeof config?.mcpServer === "object" && config.mcpServer
48
+ ? config.mcpServer
49
+ : config;
50
+ if (!mcpServer) {
51
+ return null;
52
+ }
53
+ return {
54
+ transport: normalizeMcpTransport(mcpServer.transport) ?? ((typeof mcpServer.url === "string" && mcpServer.url.trim()) ? "http" : "stdio"),
55
+ command: typeof mcpServer.command === "string" ? mcpServer.command.trim() : undefined,
56
+ args: Array.isArray(mcpServer.args) ? mcpServer.args.filter((item) => typeof item === "string") : undefined,
57
+ env: readStringRecord(mcpServer.env),
58
+ cwd: typeof mcpServer.cwd === "string" ? mcpServer.cwd : undefined,
59
+ url: typeof mcpServer.url === "string" ? mcpServer.url.trim() : undefined,
60
+ token: typeof mcpServer.token === "string" ? mcpServer.token : undefined,
61
+ headers: readStringRecord(mcpServer.headers),
62
+ };
63
+ }
64
+ function createMcpCacheKey(config) {
65
+ return JSON.stringify({
66
+ transport: config.transport ?? "stdio",
67
+ command: config.command,
68
+ args: config.args ?? [],
69
+ env: config.env ?? {},
70
+ cwd: config.cwd ?? "",
71
+ url: config.url ?? "",
72
+ token: config.token ?? "",
73
+ headers: config.headers ?? {},
74
+ });
75
+ }
76
+ export async function getOrCreateMcpClient(config) {
77
+ const cacheKey = createMcpCacheKey(config);
78
+ const cached = mcpClientCache.get(cacheKey);
79
+ if (cached) {
80
+ return cached;
81
+ }
82
+ const loading = (async () => {
83
+ const client = new Client({
84
+ name: "agent-harness",
85
+ version: AGENT_HARNESS_VERSION,
86
+ });
87
+ const headers = {
88
+ ...(config.headers ?? {}),
89
+ ...(config.token ? { Authorization: `Bearer ${config.token}` } : {}),
90
+ };
91
+ const transport = config.transport === "http"
92
+ ? new StreamableHTTPClientTransport(new URL(config.url ?? ""), {
93
+ requestInit: Object.keys(headers).length > 0 ? { headers } : undefined,
94
+ })
95
+ : config.transport === "sse"
96
+ ? new SSEClientTransport(new URL(config.url ?? ""), {
97
+ requestInit: Object.keys(headers).length > 0 ? { headers } : undefined,
98
+ })
99
+ : config.transport === "websocket"
100
+ ? new WebSocketClientTransport(new URL(config.url ?? ""))
101
+ : new StdioClientTransport({
102
+ command: config.command ?? "",
103
+ args: config.args,
104
+ env: createRuntimeEnv(config.env),
105
+ cwd: config.cwd,
106
+ });
107
+ await client.connect(transport);
108
+ return client;
109
+ })();
110
+ mcpClientCache.set(cacheKey, loading);
111
+ return loading;
112
+ }
113
+ async function getRemoteMcpToolDescriptor(config, remoteToolName) {
114
+ const client = await getOrCreateMcpClient(config);
115
+ const result = await client.listTools();
116
+ const tool = result.tools.find((item) => typeof item.name === "string" && item.name === remoteToolName);
117
+ if (!tool || typeof tool.name !== "string") {
118
+ return null;
119
+ }
120
+ return {
121
+ name: tool.name,
122
+ description: typeof tool.description === "string" ? tool.description : undefined,
123
+ inputSchema: typeof tool.inputSchema === "object" && tool.inputSchema ? tool.inputSchema : undefined,
124
+ };
125
+ }
126
+ export async function listRemoteMcpTools(config) {
127
+ const client = await getOrCreateMcpClient(config);
128
+ const result = await client.listTools();
129
+ return result.tools
130
+ .filter((tool) => typeof tool.name === "string")
131
+ .map((tool) => ({
132
+ name: tool.name,
133
+ description: typeof tool.description === "string" ? tool.description : undefined,
134
+ inputSchema: typeof tool.inputSchema === "object" && tool.inputSchema ? tool.inputSchema : undefined,
135
+ }));
136
+ }
137
+ export function createMcpToolResolver(workspace) {
138
+ const mcpTools = new Map(Array.from(workspace.tools.values())
139
+ .filter((tool) => tool.type === "mcp")
140
+ .map((tool) => [tool.id, tool]));
141
+ return (toolIds) => toolIds.flatMap((toolId) => {
142
+ const tool = mcpTools.get(toolId);
143
+ if (!tool) {
144
+ return [];
145
+ }
146
+ const serverConfig = readMcpServerConfig(workspace, tool);
147
+ if (!serverConfig) {
148
+ throw new Error(`MCP tool ${tool.id} must define mcp.serverRef or inline MCP server config.`);
149
+ }
150
+ const remoteToolName = tool.mcpRef ?? tool.name;
151
+ const descriptorPromise = getRemoteMcpToolDescriptor(serverConfig, remoteToolName);
152
+ return [
153
+ {
154
+ name: tool.name,
155
+ description: tool.description,
156
+ inputSchemaPromise: descriptorPromise.then((descriptor) => descriptor?.inputSchema),
157
+ async invoke(input) {
158
+ const client = await getOrCreateMcpClient(serverConfig);
159
+ const result = await client.callTool({
160
+ name: remoteToolName,
161
+ arguments: typeof input === "object" && input !== null ? input : {},
162
+ });
163
+ const textParts = Array.isArray(result.content)
164
+ ? result.content
165
+ .filter((item) => typeof item === "object" && item !== null && "type" in item)
166
+ .flatMap((item) => item.type === "text" && typeof item.text === "string" ? [item.text] : [])
167
+ : [];
168
+ return textParts.length > 0 ? textParts.join("\n\n") : JSON.stringify(result);
169
+ },
170
+ },
171
+ ];
172
+ });
173
+ }
@@ -1,5 +1,5 @@
1
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
1
  import type { RuntimeAdapterOptions, WorkspaceBundle } from "../contracts/types.js";
2
+ export { getOrCreateMcpClient, listRemoteMcpTools, readMcpServerConfig, type McpServerConfig, type McpToolDescriptor, } from "./mcp-tool-support.js";
3
3
  export declare function resolveLocalResourceProviderEntry(currentResourceDir: string, resolveInstalledEntry?: () => string | null): string;
4
4
  export type ResourceToolInfo = {
5
5
  toolPath: string;
@@ -12,23 +12,6 @@ export type ResourceToolInfo = {
12
12
  allow: Array<"approve" | "edit" | "reject">;
13
13
  };
14
14
  };
15
- export type McpServerConfig = {
16
- transport?: "stdio" | "http" | "sse" | "websocket";
17
- command?: string;
18
- args?: string[];
19
- env?: Record<string, string>;
20
- cwd?: string;
21
- url?: string;
22
- token?: string;
23
- headers?: Record<string, string>;
24
- };
25
- export type McpToolDescriptor = {
26
- name: string;
27
- description?: string;
28
- inputSchema?: Record<string, unknown>;
29
- };
30
- export declare function getOrCreateMcpClient(config: McpServerConfig): Promise<Client>;
31
- export declare function listRemoteMcpTools(config: McpServerConfig): Promise<McpToolDescriptor[]>;
32
15
  export declare function ensureResourceSources(sources?: string[], workspaceRoot?: string): Promise<void>;
33
16
  export declare function defaultResourceSkillsRoot(): string;
34
17
  export declare function defaultResourceConfigRoot(): string;
@@ -5,16 +5,12 @@ import { stat } from "node:fs/promises";
5
5
  import { readFile } from "node:fs/promises";
6
6
  import { fileURLToPath, pathToFileURL } from "node:url";
7
7
  import { CompositeBackend, LocalShellBackend, StateBackend, StoreBackend } from "deepagents";
8
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
9
- import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
10
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
11
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
12
- import { WebSocketClientTransport } from "@modelcontextprotocol/sdk/client/websocket.js";
13
- import { AGENT_HARNESS_VERSION } from "../package-version.js";
14
- import { isSupportedToolModulePath, loadToolModuleDefinition } from "../tool-modules.js";
15
8
  import { createRuntimeEnv } from "../runtime/support/runtime-env.js";
9
+ import { isSupportedToolModulePath, loadToolModuleDefinition } from "../tool-modules.js";
10
+ import { createMcpToolResolver, } from "./mcp-tool-support.js";
16
11
  import { resolveIsolatedResourceModulePath } from "./isolation.js";
17
12
  import { ensureExternalResourceSource, ensureExternalSource, isExternalSourceLocator, parseExternalSourceLocator } from "./sources.js";
13
+ export { getOrCreateMcpClient, listRemoteMcpTools, readMcpServerConfig, } from "./mcp-tool-support.js";
18
14
  const resourceDir = path.dirname(fileURLToPath(import.meta.url));
19
15
  const require = createRequire(import.meta.url);
20
16
  function installedResourceProviderEntry() {
@@ -81,13 +77,14 @@ function listProviderTools(provider) {
81
77
  toolPath: tool.toolPath ?? tool.builtinPath ?? "",
82
78
  }));
83
79
  }
80
+ function preferProviderValue(provider, primary, fallback) {
81
+ return provider ? (primary(provider) ?? fallback(provider)) : undefined;
82
+ }
84
83
  function createProviderToolResolver(provider, workspace, options) {
85
- return (provider?.createResourceToolResolver?.(workspace, options) ??
86
- provider?.createBuiltinToolResolver?.(workspace, options));
84
+ return preferProviderValue(provider, (candidate) => candidate.createResourceToolResolver?.(workspace, options), (candidate) => candidate.createBuiltinToolResolver?.(workspace, options));
87
85
  }
88
86
  function createProviderBackendResolver(provider, workspace) {
89
- return (provider?.createResourceBackendResolver?.(workspace) ??
90
- provider?.createBuiltinBackendResolver?.(workspace));
87
+ return preferProviderValue(provider, (candidate) => candidate.createResourceBackendResolver?.(workspace), (candidate) => candidate.createBuiltinBackendResolver?.(workspace));
91
88
  }
92
89
  class CompatibleCompositeBackend {
93
90
  id;
@@ -133,69 +130,66 @@ class CompatibleCompositeBackend {
133
130
  return this.composite.downloadFiles(paths);
134
131
  }
135
132
  }
133
+ const INLINE_BACKEND_ERROR = 'Unsupported DeepAgent backend kind "%s". Supported inline kinds: LocalShellBackend, VfsSandbox, StateBackend, StoreBackend, CompositeBackend.';
134
+ function unsupportedInlineBackend(kind) {
135
+ throw new Error(INLINE_BACKEND_ERROR.replace("%s", kind));
136
+ }
137
+ function resolveInlineBackendRootDir(workspaceRoot, configuredRootDir) {
138
+ if (typeof configuredRootDir === "string" && configuredRootDir.trim().length > 0) {
139
+ return path.isAbsolute(configuredRootDir)
140
+ ? configuredRootDir
141
+ : path.resolve(workspaceRoot, configuredRootDir);
142
+ }
143
+ return workspaceRoot;
144
+ }
145
+ function createLocalShellStyleBackend(workspaceRoot, config, options) {
146
+ const rootDir = resolveInlineBackendRootDir(workspaceRoot, config?.rootDir);
147
+ mkdirSync(rootDir, { recursive: true });
148
+ const inheritedEnv = config?.inheritEnv === false ? {} : process.env;
149
+ return new LocalShellBackend({
150
+ rootDir,
151
+ virtualMode: options.virtualMode,
152
+ timeout: typeof config?.timeout === "number" ? config.timeout : undefined,
153
+ maxOutputBytes: typeof config?.maxOutputBytes === "number" ? config.maxOutputBytes : undefined,
154
+ env: createRuntimeEnv(readStringRecord(config?.env), inheritedEnv),
155
+ inheritEnv: config?.inheritEnv !== false,
156
+ });
157
+ }
158
+ function createInlineBackendInstance(workspaceRoot, kind, config, runtimeLike) {
159
+ switch (kind) {
160
+ case "LocalShellBackend":
161
+ return createLocalShellStyleBackend(workspaceRoot, config, {
162
+ virtualMode: config?.virtualMode === true,
163
+ });
164
+ case "VfsSandbox":
165
+ return createLocalShellStyleBackend(workspaceRoot, config, {
166
+ virtualMode: config?.virtualMode === false ? false : true,
167
+ });
168
+ case "StateBackend":
169
+ return new StateBackend(runtimeLike);
170
+ case "StoreBackend":
171
+ return new StoreBackend(runtimeLike);
172
+ default:
173
+ return unsupportedInlineBackend(kind);
174
+ }
175
+ }
136
176
  function createInlineBackendResolver(workspace) {
137
177
  return (binding) => {
138
178
  const backendConfig = binding.deepAgentParams?.backend;
139
179
  if (!backendConfig || typeof backendConfig !== "object") {
140
180
  return undefined;
141
181
  }
142
- const resolveBackendRootDir = (configuredRootDir) => {
143
- if (typeof configuredRootDir === "string" && configuredRootDir.trim().length > 0) {
144
- return path.isAbsolute(configuredRootDir)
145
- ? configuredRootDir
146
- : path.resolve(workspace.workspaceRoot, configuredRootDir);
147
- }
148
- return workspace.workspaceRoot;
149
- };
150
- const createBackend = (kind, config, runtimeLike) => {
151
- const configuredEnv = typeof config?.env === "object" && config.env
152
- ? Object.fromEntries(Object.entries(config.env).filter((entry) => typeof entry[1] === "string"))
153
- : undefined;
154
- const inheritedEnv = config?.inheritEnv === false ? {} : process.env;
155
- switch (kind) {
156
- case "LocalShellBackend": {
157
- const rootDir = resolveBackendRootDir(config?.rootDir);
158
- mkdirSync(rootDir, { recursive: true });
159
- return new LocalShellBackend({
160
- rootDir,
161
- virtualMode: config?.virtualMode === true,
162
- timeout: typeof config?.timeout === "number" ? config.timeout : undefined,
163
- maxOutputBytes: typeof config?.maxOutputBytes === "number" ? config.maxOutputBytes : undefined,
164
- env: createRuntimeEnv(configuredEnv, inheritedEnv),
165
- inheritEnv: config?.inheritEnv !== false,
166
- });
167
- }
168
- case "VfsSandbox": {
169
- const rootDir = resolveBackendRootDir(config?.rootDir);
170
- mkdirSync(rootDir, { recursive: true });
171
- return new LocalShellBackend({
172
- rootDir,
173
- virtualMode: config?.virtualMode === false ? false : true,
174
- timeout: typeof config?.timeout === "number" ? config.timeout : undefined,
175
- maxOutputBytes: typeof config?.maxOutputBytes === "number" ? config.maxOutputBytes : undefined,
176
- env: createRuntimeEnv(configuredEnv, inheritedEnv),
177
- inheritEnv: config?.inheritEnv !== false,
178
- });
179
- }
180
- case "StateBackend":
181
- return new StateBackend(runtimeLike);
182
- case "StoreBackend":
183
- return new StoreBackend(runtimeLike);
184
- default:
185
- throw new Error(`Unsupported DeepAgent backend kind "${kind}". Supported inline kinds: LocalShellBackend, VfsSandbox, StateBackend, StoreBackend, CompositeBackend.`);
186
- }
187
- };
188
182
  return (runtimeLike) => {
189
183
  const kind = typeof backendConfig.kind === "string" ? backendConfig.kind : "CompositeBackend";
190
184
  switch (kind) {
191
185
  case "LocalShellBackend":
192
- return createBackend("LocalShellBackend", backendConfig, runtimeLike);
186
+ return createInlineBackendInstance(workspace.workspaceRoot, "LocalShellBackend", backendConfig, runtimeLike);
193
187
  case "VfsSandbox":
194
- return createBackend("VfsSandbox", backendConfig, runtimeLike);
188
+ return createInlineBackendInstance(workspace.workspaceRoot, "VfsSandbox", backendConfig, runtimeLike);
195
189
  case "StateBackend":
196
- return new StateBackend(runtimeLike);
190
+ return createInlineBackendInstance(workspace.workspaceRoot, "StateBackend", backendConfig, runtimeLike);
197
191
  case "StoreBackend":
198
- return new StoreBackend(runtimeLike);
192
+ return createInlineBackendInstance(workspace.workspaceRoot, "StoreBackend", backendConfig, runtimeLike);
199
193
  case "CompositeBackend": {
200
194
  const stateConfig = typeof backendConfig.state === "object" && backendConfig.state
201
195
  ? backendConfig.state
@@ -206,12 +200,12 @@ function createInlineBackendResolver(workspace) {
206
200
  : { "/memories/": { kind: "StoreBackend" } };
207
201
  const mappedRoutes = Object.fromEntries(Object.entries(routes).map(([route, routeConfig]) => {
208
202
  const routeKind = typeof routeConfig?.kind === "string" ? routeConfig.kind : "StoreBackend";
209
- return [route, createBackend(routeKind, routeConfig, runtimeLike)];
203
+ return [route, createInlineBackendInstance(workspace.workspaceRoot, routeKind, routeConfig, runtimeLike)];
210
204
  }));
211
- return new CompatibleCompositeBackend(createBackend(defaultBackendKind, stateConfig, runtimeLike), mappedRoutes);
205
+ return new CompatibleCompositeBackend(createInlineBackendInstance(workspace.workspaceRoot, defaultBackendKind, stateConfig, runtimeLike), mappedRoutes);
212
206
  }
213
207
  default:
214
- throw new Error(`Unsupported DeepAgent backend kind "${kind}". Supported inline kinds: LocalShellBackend, VfsSandbox, StateBackend, StoreBackend, CompositeBackend.`);
208
+ return unsupportedInlineBackend(kind);
215
209
  }
216
210
  };
217
211
  };
@@ -239,7 +233,23 @@ async function findPackageRoot(startPath) {
239
233
  }
240
234
  }
241
235
  const functionToolModuleCache = new Map();
242
- const mcpClientCache = new Map();
236
+ function readStringRecord(value) {
237
+ if (typeof value !== "object" || !value) {
238
+ return undefined;
239
+ }
240
+ const entries = Object.entries(value).filter((entry) => typeof entry[1] === "string");
241
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
242
+ }
243
+ function listWorkspaceResourceProviders(workspace) {
244
+ return (workspace.resourceSources ?? [])
245
+ .map((source) => remoteResourceCache.get(source))
246
+ .filter((provider) => Boolean(provider));
247
+ }
248
+ function createWorkspaceProviderResolvers(workspace, factory) {
249
+ return listWorkspaceResourceProviders(workspace)
250
+ .map((provider) => factory(provider))
251
+ .filter((resolver) => Boolean(resolver));
252
+ }
243
253
  async function loadFunctionToolModule(tool) {
244
254
  const cacheKey = `${tool.sourcePath}:${tool.implementationName ?? tool.id}`;
245
255
  const cached = functionToolModuleCache.get(cacheKey);
@@ -257,132 +267,6 @@ async function loadFunctionToolModule(tool) {
257
267
  functionToolModuleCache.set(cacheKey, loading);
258
268
  return loading;
259
269
  }
260
- function normalizeMcpTransport(value) {
261
- if (value === "stdio" || value === "http" || value === "sse" || value === "websocket") {
262
- return value;
263
- }
264
- return undefined;
265
- }
266
- function readMcpServerConfig(workspace, tool) {
267
- const mcpConfig = typeof tool.config?.mcp === "object" && tool.config?.mcp
268
- ? tool.config.mcp
269
- : undefined;
270
- const serverRef = typeof mcpConfig?.serverRef === "string" ? mcpConfig.serverRef : undefined;
271
- if (serverRef) {
272
- const serverId = serverRef.startsWith("mcp/") ? serverRef.slice(4) : serverRef;
273
- const server = workspace.mcpServers.get(serverId);
274
- if (!server) {
275
- throw new Error(`MCP tool ${tool.id} references missing MCP server ${serverRef}`);
276
- }
277
- return {
278
- transport: server.transport,
279
- command: server.command,
280
- args: server.args,
281
- env: server.env,
282
- cwd: server.cwd,
283
- url: server.url,
284
- token: server.token,
285
- headers: server.headers,
286
- };
287
- }
288
- const config = typeof tool.config === "object" && tool.config
289
- ? tool.config
290
- : undefined;
291
- const mcpServer = typeof config?.mcpServer === "object" && config.mcpServer
292
- ? config.mcpServer
293
- : config;
294
- if (!mcpServer) {
295
- return null;
296
- }
297
- const envRecord = typeof mcpServer.env === "object" && mcpServer.env
298
- ? Object.fromEntries(Object.entries(mcpServer.env).filter((entry) => typeof entry[1] === "string"))
299
- : undefined;
300
- return {
301
- transport: normalizeMcpTransport(mcpServer.transport) ?? ((typeof mcpServer.url === "string" && mcpServer.url.trim()) ? "http" : "stdio"),
302
- command: typeof mcpServer.command === "string" ? mcpServer.command.trim() : undefined,
303
- args: Array.isArray(mcpServer.args) ? mcpServer.args.filter((item) => typeof item === "string") : undefined,
304
- env: envRecord,
305
- cwd: typeof mcpServer.cwd === "string" ? mcpServer.cwd : undefined,
306
- url: typeof mcpServer.url === "string" ? mcpServer.url.trim() : undefined,
307
- token: typeof mcpServer.token === "string" ? mcpServer.token : undefined,
308
- headers: typeof mcpServer.headers === "object" && mcpServer.headers
309
- ? Object.fromEntries(Object.entries(mcpServer.headers).filter((entry) => typeof entry[1] === "string"))
310
- : undefined,
311
- };
312
- }
313
- function createMcpCacheKey(config) {
314
- return JSON.stringify({
315
- transport: config.transport ?? "stdio",
316
- command: config.command,
317
- args: config.args ?? [],
318
- env: config.env ?? {},
319
- cwd: config.cwd ?? "",
320
- url: config.url ?? "",
321
- token: config.token ?? "",
322
- headers: config.headers ?? {},
323
- });
324
- }
325
- export async function getOrCreateMcpClient(config) {
326
- const cacheKey = createMcpCacheKey(config);
327
- const cached = mcpClientCache.get(cacheKey);
328
- if (cached) {
329
- return cached;
330
- }
331
- const loading = (async () => {
332
- const client = new Client({
333
- name: "agent-harness",
334
- version: AGENT_HARNESS_VERSION,
335
- });
336
- const headers = {
337
- ...(config.headers ?? {}),
338
- ...(config.token ? { Authorization: `Bearer ${config.token}` } : {}),
339
- };
340
- const transport = config.transport === "http"
341
- ? new StreamableHTTPClientTransport(new URL(config.url ?? ""), {
342
- requestInit: Object.keys(headers).length > 0 ? { headers } : undefined,
343
- })
344
- : config.transport === "sse"
345
- ? new SSEClientTransport(new URL(config.url ?? ""), {
346
- requestInit: Object.keys(headers).length > 0 ? { headers } : undefined,
347
- })
348
- : config.transport === "websocket"
349
- ? new WebSocketClientTransport(new URL(config.url ?? ""))
350
- : new StdioClientTransport({
351
- command: config.command ?? "",
352
- args: config.args,
353
- env: createRuntimeEnv(config.env),
354
- cwd: config.cwd,
355
- });
356
- await client.connect(transport);
357
- return client;
358
- })();
359
- mcpClientCache.set(cacheKey, loading);
360
- return loading;
361
- }
362
- async function getRemoteMcpToolDescriptor(config, remoteToolName) {
363
- const client = await getOrCreateMcpClient(config);
364
- const result = await client.listTools();
365
- const tool = result.tools.find((item) => typeof item.name === "string" && item.name === remoteToolName);
366
- if (!tool || typeof tool.name !== "string") {
367
- return null;
368
- }
369
- return {
370
- name: tool.name,
371
- description: typeof tool.description === "string" ? tool.description : undefined,
372
- inputSchema: typeof tool.inputSchema === "object" && tool.inputSchema ? tool.inputSchema : undefined,
373
- };
374
- }
375
- export async function listRemoteMcpTools(config) {
376
- const client = await getOrCreateMcpClient(config);
377
- const result = await client.listTools();
378
- return result.tools
379
- .filter((tool) => typeof tool.name === "string")
380
- .map((tool) => ({
381
- name: tool.name,
382
- description: typeof tool.description === "string" ? tool.description : undefined,
383
- inputSchema: typeof tool.inputSchema === "object" && tool.inputSchema ? tool.inputSchema : undefined,
384
- }));
385
- }
386
270
  function createFunctionToolResolver(workspace) {
387
271
  const functionTools = new Map(Array.from(workspace.tools.values())
388
272
  .filter((tool) => tool.type === "function" && isSupportedToolModulePath(tool.sourcePath))
@@ -411,43 +295,6 @@ function createFunctionToolResolver(workspace) {
411
295
  ];
412
296
  });
413
297
  }
414
- function createMcpToolResolver(workspace) {
415
- const mcpTools = new Map(Array.from(workspace.tools.values())
416
- .filter((tool) => tool.type === "mcp")
417
- .map((tool) => [tool.id, tool]));
418
- return (toolIds) => toolIds.flatMap((toolId) => {
419
- const tool = mcpTools.get(toolId);
420
- if (!tool) {
421
- return [];
422
- }
423
- const serverConfig = readMcpServerConfig(workspace, tool);
424
- if (!serverConfig) {
425
- throw new Error(`MCP tool ${tool.id} must define mcp.serverRef or inline MCP server config.`);
426
- }
427
- const remoteToolName = tool.mcpRef ?? tool.name;
428
- const descriptorPromise = getRemoteMcpToolDescriptor(serverConfig, remoteToolName);
429
- return [
430
- {
431
- name: tool.name,
432
- description: tool.description,
433
- inputSchemaPromise: descriptorPromise.then((descriptor) => descriptor?.inputSchema),
434
- async invoke(input) {
435
- const client = await getOrCreateMcpClient(serverConfig);
436
- const result = await client.callTool({
437
- name: remoteToolName,
438
- arguments: typeof input === "object" && input !== null ? input : {},
439
- });
440
- const textParts = Array.isArray(result.content)
441
- ? result.content
442
- .filter((item) => typeof item === "object" && item !== null && "type" in item)
443
- .flatMap((item) => item.type === "text" && typeof item.text === "string" ? [item.text] : [])
444
- : [];
445
- return textParts.length > 0 ? textParts.join("\n\n") : JSON.stringify(result);
446
- },
447
- },
448
- ];
449
- });
450
- }
451
298
  function resolvePackageEntry(packageRoot, pkg) {
452
299
  const exportsField = pkg.exports;
453
300
  if (typeof exportsField === "string") {
@@ -514,11 +361,11 @@ export async function ensureResourceSources(sources = [], workspaceRoot = proces
514
361
  }
515
362
  export function defaultResourceSkillsRoot() {
516
363
  const provider = requireLocalResource("default resource skill resolution");
517
- return provider.defaultResourceSkillsRoot?.() ?? provider.builtinSkillsRoot?.() ?? "";
364
+ return preferProviderValue(provider, (candidate) => candidate.defaultResourceSkillsRoot?.(), (candidate) => candidate.builtinSkillsRoot?.()) ?? "";
518
365
  }
519
366
  export function defaultResourceConfigRoot() {
520
367
  const provider = requireLocalResource("default resource config resolution");
521
- return provider.defaultResourceConfigRoot?.() ?? provider.builtinConfigRoot?.() ?? provider.builtinDefaultsRoot?.() ?? "";
368
+ return (preferProviderValue(provider, (candidate) => candidate.defaultResourceConfigRoot?.(), (candidate) => candidate.builtinConfigRoot?.() ?? candidate.builtinDefaultsRoot?.()) ?? "");
522
369
  }
523
370
  export async function listResourceTools(sources = [], workspaceRoot = process.cwd()) {
524
371
  await ensureResourceSources(sources, workspaceRoot);
@@ -539,11 +386,7 @@ export async function listResourceToolsForSource(source, workspaceRoot = process
539
386
  }
540
387
  export function createResourceBackendResolver(workspace) {
541
388
  const localResolver = createProviderBackendResolver(localResource, workspace);
542
- const remoteResolvers = (workspace.resourceSources ?? [])
543
- .map((source) => remoteResourceCache.get(source))
544
- .filter((provider) => Boolean(provider))
545
- .map((provider) => createProviderBackendResolver(provider, workspace))
546
- .filter((resolver) => Boolean(resolver));
389
+ const remoteResolvers = createWorkspaceProviderResolvers(workspace, (provider) => createProviderBackendResolver(provider, workspace));
547
390
  const inlineResolver = createInlineBackendResolver(workspace);
548
391
  return (binding) => {
549
392
  const providerResolved = localResolver?.(binding) ??
@@ -555,11 +398,7 @@ export function createResourceToolResolver(workspace, options = {}) {
555
398
  const functionResolver = createFunctionToolResolver(workspace);
556
399
  const mcpResolver = createMcpToolResolver(workspace);
557
400
  const localResolver = createProviderToolResolver(localResource, workspace, options);
558
- const remoteResolvers = (workspace.resourceSources ?? [])
559
- .map((source) => remoteResourceCache.get(source))
560
- .filter((provider) => Boolean(provider))
561
- .map((provider) => createProviderToolResolver(provider, workspace, options))
562
- .filter((resolver) => Boolean(resolver));
401
+ const remoteResolvers = createWorkspaceProviderResolvers(workspace, (provider) => createProviderToolResolver(provider, workspace, options));
563
402
  return (toolIds, binding) => {
564
403
  const resolved = [
565
404
  ...functionResolver(toolIds, binding),
@@ -0,0 +1,22 @@
1
+ import type { CompiledAgentBinding, CompiledTool } from "../../contracts/types.js";
2
+ import type { ExecutedToolResult } from "./invocation-result.js";
3
+ import type { ToolNameMapping } from "./tool/tool-name-mapping.js";
4
+ export type ExecutableTool = {
5
+ name: string;
6
+ schema: unknown;
7
+ invoke: (input: unknown) => Promise<unknown>;
8
+ };
9
+ export declare function invokeRuntimeWithLocalTools(options: {
10
+ binding: CompiledAgentBinding;
11
+ request: unknown;
12
+ resumePayload?: unknown;
13
+ primaryTools: CompiledTool[];
14
+ defersToUpstreamHitlExecution: boolean;
15
+ toolNameMapping: ToolNameMapping;
16
+ executableTools: Map<string, ExecutableTool>;
17
+ builtinExecutableTools: Map<string, ExecutableTool>;
18
+ callRuntimeWithToolParseRecovery: (request: unknown) => Promise<Record<string, unknown>>;
19
+ }): Promise<{
20
+ result: Record<string, unknown>;
21
+ executedToolResults: ExecutedToolResult[];
22
+ }>;
@@ -0,0 +1,18 @@
1
+ import { runLocalToolInvocationLoop } from "./local-tool-invocation.js";
2
+ export async function invokeRuntimeWithLocalTools(options) {
3
+ if (options.resumePayload !== undefined || options.defersToUpstreamHitlExecution) {
4
+ return {
5
+ result: await options.callRuntimeWithToolParseRecovery(options.request),
6
+ executedToolResults: [],
7
+ };
8
+ }
9
+ return runLocalToolInvocationLoop({
10
+ binding: options.binding,
11
+ request: options.request,
12
+ primaryTools: options.primaryTools,
13
+ toolNameMapping: options.toolNameMapping,
14
+ executableTools: options.executableTools,
15
+ builtinExecutableTools: options.builtinExecutableTools,
16
+ callRuntimeWithToolParseRecovery: options.callRuntimeWithToolParseRecovery,
17
+ });
18
+ }