@botbotgo/agent-harness 0.0.307 → 0.0.309
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 +46 -10
- package/README.zh.md +46 -10
- package/dist/api.d.ts +3 -1
- package/dist/api.js +3 -0
- package/dist/cli.d.ts +12 -1
- package/dist/cli.js +331 -41
- package/dist/config/runtime/workspace.yaml +16 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +14 -0
- package/dist/mcp.js +175 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/resource/isolation.js +6 -1
- package/dist/runtime/adapter/model/invocation-request.d.ts +1 -0
- package/dist/runtime/adapter/model/invocation-request.js +4 -0
- package/dist/runtime/harness.d.ts +1 -0
- package/dist/runtime/harness.js +4 -0
- package/dist/workspace/compile.js +135 -5
- package/dist/workspace/object-loader.js +27 -17
- package/dist/workspace/resource-compilers.js +3 -3
- package/dist/workspace/support/source-protocols.d.ts +19 -0
- package/dist/workspace/support/source-protocols.js +192 -0
- package/dist/workspace/support/workspace-ref-utils.d.ts +4 -0
- package/dist/workspace/support/workspace-ref-utils.js +4 -0
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
2
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { createServer } from "node:http";
|
|
3
6
|
import { pathToFileURL } from "node:url";
|
|
4
7
|
import { z } from "zod";
|
|
5
8
|
import { AGENT_HARNESS_VERSION } from "./package-version.js";
|
|
@@ -46,6 +49,33 @@ function renderToolOutput(output) {
|
|
|
46
49
|
}
|
|
47
50
|
return JSON.stringify(output, null, 2);
|
|
48
51
|
}
|
|
52
|
+
function normalizePath(value, fallback) {
|
|
53
|
+
const source = typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback;
|
|
54
|
+
return source.startsWith("/") ? source : `/${source}`;
|
|
55
|
+
}
|
|
56
|
+
function writeJson(response, statusCode, payload) {
|
|
57
|
+
response.statusCode = statusCode;
|
|
58
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
59
|
+
response.end(JSON.stringify(payload));
|
|
60
|
+
}
|
|
61
|
+
function readRequestBody(request) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const chunks = [];
|
|
64
|
+
request.on("data", (chunk) => {
|
|
65
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
66
|
+
});
|
|
67
|
+
request.on("end", () => {
|
|
68
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
69
|
+
});
|
|
70
|
+
request.on("error", reject);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function isInitializeRequest(payload) {
|
|
74
|
+
return typeof payload === "object"
|
|
75
|
+
&& payload !== null
|
|
76
|
+
&& !Array.isArray(payload)
|
|
77
|
+
&& payload.method === "initialize";
|
|
78
|
+
}
|
|
49
79
|
function jsonSchemaObjectToRawShape(schema) {
|
|
50
80
|
if (!schema || schema.type !== "object") {
|
|
51
81
|
return undefined;
|
|
@@ -251,3 +281,148 @@ export async function serveRuntimeMcpOverStdio(runtime, options = {}) {
|
|
|
251
281
|
await server.connect(new StdioServerTransport());
|
|
252
282
|
return server;
|
|
253
283
|
}
|
|
284
|
+
export async function serveRuntimeMcpOverStreamableHttp(runtime, options = {}) {
|
|
285
|
+
const hostname = options.hostname?.trim() || "127.0.0.1";
|
|
286
|
+
const port = typeof options.port === "number" && Number.isFinite(options.port) ? options.port : 0;
|
|
287
|
+
const mcpPath = normalizePath(options.path, "/mcp");
|
|
288
|
+
const sessions = new Map();
|
|
289
|
+
const closeSession = async (sessionId) => {
|
|
290
|
+
const session = sessions.get(sessionId);
|
|
291
|
+
if (!session) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
sessions.delete(sessionId);
|
|
295
|
+
await Promise.allSettled([
|
|
296
|
+
session.transport.close(),
|
|
297
|
+
session.server.close(),
|
|
298
|
+
]);
|
|
299
|
+
};
|
|
300
|
+
const httpServer = createServer(async (request, response) => {
|
|
301
|
+
try {
|
|
302
|
+
const requestUrl = new URL(request.url ?? "/", `http://${hostname}`);
|
|
303
|
+
if (requestUrl.pathname !== mcpPath) {
|
|
304
|
+
writeJson(response, 404, {
|
|
305
|
+
error: "Not Found",
|
|
306
|
+
path: mcpPath,
|
|
307
|
+
});
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const headerValue = request.headers["mcp-session-id"];
|
|
311
|
+
const sessionId = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
312
|
+
if (request.method === "POST") {
|
|
313
|
+
const body = await readRequestBody(request);
|
|
314
|
+
let payload;
|
|
315
|
+
try {
|
|
316
|
+
payload = JSON.parse(body);
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
writeJson(response, 400, {
|
|
320
|
+
jsonrpc: "2.0",
|
|
321
|
+
id: null,
|
|
322
|
+
error: {
|
|
323
|
+
code: -32700,
|
|
324
|
+
message: "Invalid JSON payload.",
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const existingSession = sessionId ? sessions.get(sessionId) : undefined;
|
|
330
|
+
if (existingSession) {
|
|
331
|
+
await existingSession.transport.handleRequest(request, response, payload);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (sessionId || !isInitializeRequest(payload)) {
|
|
335
|
+
writeJson(response, 400, {
|
|
336
|
+
jsonrpc: "2.0",
|
|
337
|
+
id: null,
|
|
338
|
+
error: {
|
|
339
|
+
code: -32000,
|
|
340
|
+
message: "Bad Request: No valid MCP session was provided.",
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
let createdTransport;
|
|
346
|
+
const server = await createRuntimeMcpServer(runtime, options);
|
|
347
|
+
const transport = new StreamableHTTPServerTransport({
|
|
348
|
+
sessionIdGenerator: () => randomUUID(),
|
|
349
|
+
onsessioninitialized: (initializedSessionId) => {
|
|
350
|
+
sessions.set(initializedSessionId, { transport, server });
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
createdTransport = transport;
|
|
354
|
+
transport.onclose = () => {
|
|
355
|
+
const activeSessionId = createdTransport?.sessionId;
|
|
356
|
+
if (activeSessionId) {
|
|
357
|
+
const active = sessions.get(activeSessionId);
|
|
358
|
+
if (active?.transport === createdTransport) {
|
|
359
|
+
sessions.delete(activeSessionId);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
await server.connect(transport);
|
|
364
|
+
await transport.handleRequest(request, response, payload);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (request.method === "GET" || request.method === "DELETE") {
|
|
368
|
+
if (!sessionId || !sessions.has(sessionId)) {
|
|
369
|
+
writeJson(response, 400, {
|
|
370
|
+
jsonrpc: "2.0",
|
|
371
|
+
id: null,
|
|
372
|
+
error: {
|
|
373
|
+
code: -32000,
|
|
374
|
+
message: "Bad Request: No valid MCP session was provided.",
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const session = sessions.get(sessionId);
|
|
380
|
+
await session.transport.handleRequest(request, response);
|
|
381
|
+
if (request.method === "DELETE") {
|
|
382
|
+
await closeSession(sessionId);
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
response.statusCode = 405;
|
|
387
|
+
response.setHeader("allow", "GET, POST, DELETE");
|
|
388
|
+
response.end();
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
writeJson(response, 500, {
|
|
392
|
+
error: error instanceof Error ? error.message : "Runtime MCP Streamable HTTP transport failed.",
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
const completed = new Promise((resolve, reject) => {
|
|
397
|
+
httpServer.once("close", resolve);
|
|
398
|
+
httpServer.once("error", reject);
|
|
399
|
+
});
|
|
400
|
+
await new Promise((resolve, reject) => {
|
|
401
|
+
httpServer.listen(port, hostname, () => resolve());
|
|
402
|
+
httpServer.once("error", reject);
|
|
403
|
+
});
|
|
404
|
+
const address = httpServer.address();
|
|
405
|
+
const resolvedPort = typeof address === "object" && address ? address.port : port;
|
|
406
|
+
return {
|
|
407
|
+
hostname,
|
|
408
|
+
port: resolvedPort,
|
|
409
|
+
path: mcpPath,
|
|
410
|
+
url: `http://${hostname}:${resolvedPort}${mcpPath}`,
|
|
411
|
+
completed,
|
|
412
|
+
close: async () => {
|
|
413
|
+
const sessionIds = Array.from(sessions.keys());
|
|
414
|
+
for (const sessionId of sessionIds) {
|
|
415
|
+
await closeSession(sessionId);
|
|
416
|
+
}
|
|
417
|
+
await new Promise((resolve, reject) => {
|
|
418
|
+
httpServer.close((error) => {
|
|
419
|
+
if (error) {
|
|
420
|
+
reject(error);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
resolve();
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.308";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.308";
|
|
@@ -93,7 +93,9 @@ async function linkHarnessPackage(isolatedRoot) {
|
|
|
93
93
|
}
|
|
94
94
|
async function buildIsolatedResourceRoot(packageRoot) {
|
|
95
95
|
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
96
|
-
const manifest =
|
|
96
|
+
const manifest = existsSync(packageJsonPath)
|
|
97
|
+
? JSON.parse(await readFile(packageJsonPath, "utf8"))
|
|
98
|
+
: {};
|
|
97
99
|
const isolatedRoot = createIsolatedSnapshotDir(packageRoot);
|
|
98
100
|
await mkdir(path.dirname(isolatedRoot), { recursive: true });
|
|
99
101
|
await cp(packageRoot, isolatedRoot, {
|
|
@@ -101,6 +103,9 @@ async function buildIsolatedResourceRoot(packageRoot) {
|
|
|
101
103
|
force: true,
|
|
102
104
|
filter: (source) => path.basename(source) !== "node_modules",
|
|
103
105
|
});
|
|
106
|
+
if (!existsSync(path.join(isolatedRoot, "package.json"))) {
|
|
107
|
+
await cp(path.join(HARNESS_PACKAGE_ROOT, "resources", "package.json"), path.join(isolatedRoot, "package.json"));
|
|
108
|
+
}
|
|
104
109
|
await mkdir(path.join(isolatedRoot, "node_modules"), { recursive: true });
|
|
105
110
|
await linkDeclaredDependencies(isolatedRoot, packageRoot, manifest);
|
|
106
111
|
await linkHarnessPackage(isolatedRoot);
|
|
@@ -2,6 +2,7 @@ import type { CompiledAgentBinding, MessageContent, TranscriptMessage } from "..
|
|
|
2
2
|
export declare function buildAgentMessages(history: TranscriptMessage[], input: MessageContent, options?: {
|
|
3
3
|
suppressExplicitResourceTurns?: boolean;
|
|
4
4
|
suppressAssistantTurns?: boolean;
|
|
5
|
+
suppressHistoryTurns?: boolean;
|
|
5
6
|
}): Array<{
|
|
6
7
|
role: string;
|
|
7
8
|
content: MessageContent;
|
|
@@ -84,6 +84,9 @@ function selectRelevantHistoryTurns(groupedHistory, inputText, options = {}) {
|
|
|
84
84
|
}
|
|
85
85
|
export function buildAgentMessages(history, input, options = {}) {
|
|
86
86
|
const inputText = extractMessageText(input).trim();
|
|
87
|
+
if (options.suppressHistoryTurns) {
|
|
88
|
+
return [{ role: "user", content: normalizeMessageContent(input) }];
|
|
89
|
+
}
|
|
87
90
|
const groupedHistory = history.reduce((groups, item) => {
|
|
88
91
|
const current = groups.at(-1);
|
|
89
92
|
if (current && current[0]?.requestId === item.requestId) {
|
|
@@ -156,6 +159,7 @@ export function buildInvocationRequest(binding, history, input, options = {}) {
|
|
|
156
159
|
const messages = buildAgentMessages(history, input, {
|
|
157
160
|
suppressExplicitResourceTurns: Boolean(memoryInstruction) && !hasExplicitResourceReference(inputText),
|
|
158
161
|
suppressAssistantTurns: Boolean(memoryInstruction) && !hasExplicitResourceReference(inputText),
|
|
162
|
+
suppressHistoryTurns: Boolean(memoryInstruction) && !hasExplicitResourceReference(inputText),
|
|
159
163
|
});
|
|
160
164
|
const contextualFollowUpInstruction = buildContextualFollowUpInstruction(inputText, Boolean(memoryInstruction));
|
|
161
165
|
const conversationLanguage = resolveConversationLanguage(history, inputText);
|
|
@@ -125,6 +125,7 @@ export declare class AgentHarnessRuntime {
|
|
|
125
125
|
serveToolsOverStdio(options: ToolMcpServerOptions): Promise<import("@modelcontextprotocol/sdk/server/mcp").McpServer>;
|
|
126
126
|
createRuntimeMcpServer(options?: RuntimeMcpServerOptions): Promise<import("@modelcontextprotocol/sdk/server/mcp").McpServer>;
|
|
127
127
|
serveRuntimeMcpOverStdio(options?: RuntimeMcpServerOptions): Promise<import("@modelcontextprotocol/sdk/server/mcp").McpServer>;
|
|
128
|
+
serveRuntimeMcpOverStreamableHttp(options?: import("../mcp.js").RuntimeMcpStreamableHttpServerOptions): Promise<import("../mcp.js").RuntimeMcpStreamableHttpServer>;
|
|
128
129
|
routeAgent(input: MessageContent, options?: {
|
|
129
130
|
sessionId?: string;
|
|
130
131
|
}): Promise<string>;
|
package/dist/runtime/harness.js
CHANGED
|
@@ -1049,6 +1049,10 @@ export class AgentHarnessRuntime {
|
|
|
1049
1049
|
async serveRuntimeMcpOverStdio(options = {}) {
|
|
1050
1050
|
return serveRuntimeMcpOverStdio(this, options);
|
|
1051
1051
|
}
|
|
1052
|
+
async serveRuntimeMcpOverStreamableHttp(options = {}) {
|
|
1053
|
+
const { serveRuntimeMcpOverStreamableHttp } = await import("../mcp.js");
|
|
1054
|
+
return serveRuntimeMcpOverStreamableHttp(this, options);
|
|
1055
|
+
}
|
|
1052
1056
|
async routeAgent(input, options = {}) {
|
|
1053
1057
|
return routeAgentId({
|
|
1054
1058
|
workspace: this.workspace,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { readdir } from "node:fs/promises";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
4
5
|
import { ensureResourceSources } from "../resource/resource.js";
|
|
5
6
|
import { ensureExternalResourceSource, isExternalSourceLocator, resolveResourcePackageRoot } from "../resource/sources.js";
|
|
6
7
|
import { loadWorkspaceObjects, readToolModuleItems, readYamlItems } from "./object-loader.js";
|
|
@@ -10,10 +11,30 @@ import { validateAgent, validateTopology } from "./validate.js";
|
|
|
10
11
|
import { compileBinding } from "./agent-binding-compiler.js";
|
|
11
12
|
import { discoverSubagents, ensureDiscoverySources } from "./support/discovery.js";
|
|
12
13
|
import { collectAgentDiscoverySourceRefs, collectToolSourceRefs } from "./support/source-collectors.js";
|
|
13
|
-
import { getRoutingDefaultAgentId, getRuntimeResources, getRuntimeStorageRoots, getToolModuleDiscoveryConfig, getRoutingRules, resolveRefId, } from "./support/workspace-ref-utils.js";
|
|
14
|
+
import { getRoutingDefaultAgentId, getRuntimeSources, getRuntimeResources, getRuntimeStorageRoots, getToolModuleDiscoveryConfig, getRoutingRules, resolveRefId, } from "./support/workspace-ref-utils.js";
|
|
14
15
|
import { hydrateAgentMcpTools, hydrateResourceAndExternalTools } from "./tool-hydration.js";
|
|
15
16
|
import { traceStartupStage } from "../runtime/startup-tracing.js";
|
|
16
17
|
import { shouldSkipScanDirectory } from "../utils/fs.js";
|
|
18
|
+
import { ensureRemoteSkillSource, ensureToolPackageSource, isFileSourceUri, isHttpSourceUri, isNpmSourceUri, resolveFileSourcePath, } from "./support/source-protocols.js";
|
|
19
|
+
import { discoverToolModuleDefinitions } from "../tool-modules.js";
|
|
20
|
+
function mergeObjectValues(base, override) {
|
|
21
|
+
if (override === undefined) {
|
|
22
|
+
return base;
|
|
23
|
+
}
|
|
24
|
+
if (typeof base === "object" &&
|
|
25
|
+
base &&
|
|
26
|
+
typeof override === "object" &&
|
|
27
|
+
override &&
|
|
28
|
+
!Array.isArray(base) &&
|
|
29
|
+
!Array.isArray(override)) {
|
|
30
|
+
const merged = { ...base };
|
|
31
|
+
for (const [key, value] of Object.entries(override)) {
|
|
32
|
+
merged[key] = key in merged ? mergeObjectValues(merged[key], value) : value;
|
|
33
|
+
}
|
|
34
|
+
return merged;
|
|
35
|
+
}
|
|
36
|
+
return override;
|
|
37
|
+
}
|
|
17
38
|
function collectParsedResources(refs) {
|
|
18
39
|
const embeddings = new Map();
|
|
19
40
|
const mcpServers = new Map();
|
|
@@ -132,7 +153,7 @@ async function registerAttachedResourceTools(tools, resourceRoot, toolModuleDisc
|
|
|
132
153
|
sourcePath,
|
|
133
154
|
value: item,
|
|
134
155
|
});
|
|
135
|
-
tools.set(parsed.id, parsed);
|
|
156
|
+
tools.set(parsed.id, mergeParsedToolObject(tools.get(parsed.id), parsed));
|
|
136
157
|
}
|
|
137
158
|
for (const { item, sourcePath } of await readToolModuleItems(toolsRoot, { scope: toolModuleDiscoveryScope })) {
|
|
138
159
|
const parsed = parseToolObject({
|
|
@@ -141,7 +162,7 @@ async function registerAttachedResourceTools(tools, resourceRoot, toolModuleDisc
|
|
|
141
162
|
sourcePath,
|
|
142
163
|
value: item,
|
|
143
164
|
});
|
|
144
|
-
tools.set(parsed.id, parsed);
|
|
165
|
+
tools.set(parsed.id, mergeParsedToolObject(tools.get(parsed.id), parsed));
|
|
145
166
|
}
|
|
146
167
|
}
|
|
147
168
|
async function collectSkillRoots(root) {
|
|
@@ -160,6 +181,90 @@ async function collectSkillRoots(root) {
|
|
|
160
181
|
return [];
|
|
161
182
|
}
|
|
162
183
|
}
|
|
184
|
+
async function resolveConfiguredSkillSourceRoots(sources, workspaceRoot) {
|
|
185
|
+
const resolved = [];
|
|
186
|
+
for (const source of sources) {
|
|
187
|
+
if (isFileSourceUri(source)) {
|
|
188
|
+
const resolvedPath = resolveFileSourcePath(source, workspaceRoot);
|
|
189
|
+
resolved.push(resolvedPath.endsWith(`${path.sep}SKILL.md`) || resolvedPath.endsWith("/SKILL.md")
|
|
190
|
+
? path.dirname(resolvedPath)
|
|
191
|
+
: resolvedPath);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (isHttpSourceUri(source)) {
|
|
195
|
+
resolved.push(await ensureRemoteSkillSource(source));
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
throw new Error(`Unsupported skill source ${source}. Use file:// or https://.`);
|
|
199
|
+
}
|
|
200
|
+
return resolved;
|
|
201
|
+
}
|
|
202
|
+
async function registerToolFolderSource(tools, folderRoot, toolModuleDiscoveryScope) {
|
|
203
|
+
for (const { item, sourcePath } of await readYamlItems(folderRoot, undefined, { recursive: true })) {
|
|
204
|
+
const parsed = parseToolObject({
|
|
205
|
+
id: typeof item.id === "string" ? item.id : path.basename(sourcePath).replace(/\.(yaml|yml|json)$/i, ""),
|
|
206
|
+
kind: "tool",
|
|
207
|
+
sourcePath,
|
|
208
|
+
value: item,
|
|
209
|
+
});
|
|
210
|
+
tools.set(parsed.id, mergeParsedToolObject(tools.get(parsed.id), parsed));
|
|
211
|
+
}
|
|
212
|
+
for (const { item, sourcePath } of await readToolModuleItems(folderRoot, { scope: toolModuleDiscoveryScope })) {
|
|
213
|
+
const parsed = parseToolObject({
|
|
214
|
+
id: String(item.id),
|
|
215
|
+
kind: "tool",
|
|
216
|
+
sourcePath,
|
|
217
|
+
value: item,
|
|
218
|
+
});
|
|
219
|
+
tools.set(parsed.id, mergeParsedToolObject(tools.get(parsed.id), parsed));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function registerToolPackageSource(tools, source, workspaceRoot) {
|
|
223
|
+
const installed = await ensureToolPackageSource(source, workspaceRoot);
|
|
224
|
+
const imported = await import(pathToFileURL(installed.entryPath).href);
|
|
225
|
+
const definitions = discoverToolModuleDefinitions("", imported);
|
|
226
|
+
for (const definition of definitions) {
|
|
227
|
+
const parsed = parseToolObject({
|
|
228
|
+
id: definition.implementationName,
|
|
229
|
+
kind: "tool",
|
|
230
|
+
sourcePath: installed.entryPath,
|
|
231
|
+
value: {
|
|
232
|
+
kind: "tool",
|
|
233
|
+
id: definition.implementationName,
|
|
234
|
+
type: "function",
|
|
235
|
+
name: definition.implementationName,
|
|
236
|
+
description: definition.description,
|
|
237
|
+
implementationName: definition.implementationName,
|
|
238
|
+
hasModuleSchema: definition.hasModuleSchema,
|
|
239
|
+
...(definition.modelSchema ? { modelSchema: definition.modelSchema } : {}),
|
|
240
|
+
...(definition.retryable !== undefined ? { retryable: definition.retryable } : {}),
|
|
241
|
+
...(definition.memory ? { config: { memory: definition.memory } } : {}),
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
tools.set(parsed.id, mergeParsedToolObject(tools.get(parsed.id), parsed));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function mergeParsedToolObject(current, incoming) {
|
|
248
|
+
if (!current) {
|
|
249
|
+
return incoming;
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
...current,
|
|
253
|
+
...incoming,
|
|
254
|
+
config: mergeObjectValues(current.config, incoming.config),
|
|
255
|
+
subprocess: incoming.subprocess ?? current.subprocess,
|
|
256
|
+
inputSchemaRef: incoming.inputSchemaRef ?? current.inputSchemaRef,
|
|
257
|
+
hasModuleSchema: incoming.hasModuleSchema ?? current.hasModuleSchema,
|
|
258
|
+
modelSchema: incoming.modelSchema ?? current.modelSchema,
|
|
259
|
+
embeddingModelRef: incoming.embeddingModelRef ?? current.embeddingModelRef,
|
|
260
|
+
backendOperation: incoming.backendOperation ?? current.backendOperation,
|
|
261
|
+
mcpRef: incoming.mcpRef ?? current.mcpRef,
|
|
262
|
+
bundleRefs: incoming.bundleRefs.length > 0 ? incoming.bundleRefs : current.bundleRefs,
|
|
263
|
+
hitl: incoming.hitl ?? current.hitl,
|
|
264
|
+
retryable: incoming.retryable ?? current.retryable,
|
|
265
|
+
sourcePath: incoming.sourcePath,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
163
268
|
async function registerWorkspaceSkillRegistry(skillCollectionRoots) {
|
|
164
269
|
const registry = new Map();
|
|
165
270
|
for (const skillsRoot of skillCollectionRoots) {
|
|
@@ -227,10 +332,29 @@ export async function loadWorkspace(workspaceRoot, options = {}) {
|
|
|
227
332
|
}
|
|
228
333
|
const { embeddings, mcpServers, models, vectorStores, tools } = collectParsedResources(loaded.refs);
|
|
229
334
|
const toolModuleDiscoveryConfig = getToolModuleDiscoveryConfig(loaded.refs);
|
|
335
|
+
const runtimeSources = getRuntimeSources(loaded.refs);
|
|
230
336
|
await traceStartupStage("workspace.hydrate.agentMcpTools", () => hydrateAgentMcpTools(loaded.agents, mcpServers, tools), {
|
|
231
337
|
workspaceRoot,
|
|
232
338
|
agentCount: loaded.agents.length,
|
|
233
339
|
});
|
|
340
|
+
for (const source of runtimeSources.tools) {
|
|
341
|
+
if (isFileSourceUri(source)) {
|
|
342
|
+
const folderRoot = resolveFileSourcePath(source, workspaceRoot);
|
|
343
|
+
await traceStartupStage("workspace.register.toolFolderSource", () => registerToolFolderSource(tools, folderRoot, toolModuleDiscoveryConfig.scope), {
|
|
344
|
+
workspaceRoot,
|
|
345
|
+
source,
|
|
346
|
+
});
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (isNpmSourceUri(source)) {
|
|
350
|
+
await traceStartupStage("workspace.register.toolPackageSource", () => registerToolPackageSource(tools, source, workspaceRoot), {
|
|
351
|
+
workspaceRoot,
|
|
352
|
+
source,
|
|
353
|
+
});
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
throw new Error(`Unsupported tool source ${source}. Use file:// or npm://.`);
|
|
357
|
+
}
|
|
234
358
|
const configuredResources = Array.from(new Set([
|
|
235
359
|
...getRuntimeResources(loaded.refs),
|
|
236
360
|
...(options.resources ?? []),
|
|
@@ -246,11 +370,16 @@ export async function loadWorkspace(workspaceRoot, options = {}) {
|
|
|
246
370
|
});
|
|
247
371
|
}
|
|
248
372
|
const localResourceRoot = resolveResourcePackageRoot(workspaceRoot);
|
|
249
|
-
const
|
|
373
|
+
const configuredSkillSourceRoots = await traceStartupStage("workspace.resolve.skillSources", () => resolveConfiguredSkillSourceRoots(runtimeSources.skills, workspaceRoot), {
|
|
374
|
+
workspaceRoot,
|
|
375
|
+
skillSourceCount: runtimeSources.skills.length,
|
|
376
|
+
});
|
|
377
|
+
const skillCollectionRoots = Array.from(new Set([
|
|
250
378
|
path.join(workspaceRoot, "modules", "skills"),
|
|
251
379
|
...(localResourceRoot ? [path.join(localResourceRoot, "skills")] : []),
|
|
380
|
+
...configuredSkillSourceRoots,
|
|
252
381
|
...resolvedConfiguredResources.map((resource) => path.join(resource.root, "skills")),
|
|
253
|
-
];
|
|
382
|
+
]));
|
|
254
383
|
const skillRegistry = await traceStartupStage("workspace.register.skillRegistry", () => registerWorkspaceSkillRegistry(skillCollectionRoots), {
|
|
255
384
|
workspaceRoot,
|
|
256
385
|
skillCollectionRootCount: skillCollectionRoots.length,
|
|
@@ -272,6 +401,7 @@ export async function loadWorkspace(workspaceRoot, options = {}) {
|
|
|
272
401
|
validateToolNameConflicts(tools);
|
|
273
402
|
const resources = Array.from(new Set([
|
|
274
403
|
...(localResourceRoot ? [localResourceRoot] : []),
|
|
404
|
+
...runtimeSources.tools.filter((source) => isNpmSourceUri(source)),
|
|
275
405
|
...collectedResources,
|
|
276
406
|
]));
|
|
277
407
|
await traceStartupStage("workspace.validate.resources", async () => {
|
|
@@ -6,6 +6,7 @@ import { resolveIsolatedResourceModulePath } from "../resource/isolation.js";
|
|
|
6
6
|
import { isExternalSourceLocator, resolveResourcePackageRoot } from "../resource/sources.js";
|
|
7
7
|
import { discoverToolModuleDefinitions, isSupportedToolModulePath, loadToolModuleDefinition } from "../tool-modules.js";
|
|
8
8
|
import { fileExists, shouldSkipScanDirectory } from "../utils/fs.js";
|
|
9
|
+
import { isFileSourceUri, readRuntimeSources, resolveFileSourcePath } from "./support/source-protocols.js";
|
|
9
10
|
import { readNamedYamlItems, readYamlItems, } from "./yaml-object-reader.js";
|
|
10
11
|
export { normalizeYamlItem, readYamlItems } from "./yaml-object-reader.js";
|
|
11
12
|
const CONVENTIONAL_OBJECT_DIRECTORIES = ["tools"];
|
|
@@ -682,23 +683,32 @@ function getMergedToolModuleDiscoveryScope(mergedObjects) {
|
|
|
682
683
|
: {};
|
|
683
684
|
return toolModuleDiscovery.scope === "top-level" ? "top-level" : "recursive";
|
|
684
685
|
}
|
|
685
|
-
async function loadConventionalObjectsForRoot(root, mergedObjects, toolModuleDiscoveryScope) {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
686
|
+
async function loadConventionalObjectsForRoot(root, runtimeRoot, mergedObjects, toolModuleDiscoveryScope) {
|
|
687
|
+
const runtimeDefaults = mergedObjects.get("runtime/default")?.item;
|
|
688
|
+
const configuredToolRoots = readRuntimeSources(runtimeDefaults).tools
|
|
689
|
+
.filter((source) => isFileSourceUri(source))
|
|
690
|
+
.map((source) => resolveFileSourcePath(source, runtimeRoot));
|
|
691
|
+
const conventionalToolRoots = CONVENTIONAL_OBJECT_DIRECTORIES.flatMap((directory) => conventionalDirectoryRoots(root, directory));
|
|
692
|
+
const objectRoots = root === runtimeRoot
|
|
693
|
+
? Array.from(new Set([
|
|
694
|
+
...conventionalToolRoots,
|
|
695
|
+
...configuredToolRoots,
|
|
696
|
+
]))
|
|
697
|
+
: conventionalToolRoots;
|
|
698
|
+
for (const objectRoot of objectRoots) {
|
|
699
|
+
for (const { item, sourcePath } of await readYamlItemsIgnoringNodeModules(objectRoot)) {
|
|
700
|
+
const workspaceObject = parseWorkspaceObject(item, sourcePath);
|
|
701
|
+
if (!workspaceObject) {
|
|
702
|
+
continue;
|
|
694
703
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
704
|
+
mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
|
|
705
|
+
}
|
|
706
|
+
for (const { item, sourcePath } of await readToolModuleItems(objectRoot, { scope: toolModuleDiscoveryScope })) {
|
|
707
|
+
const workspaceObject = parseWorkspaceObject(item, sourcePath);
|
|
708
|
+
if (!workspaceObject) {
|
|
709
|
+
continue;
|
|
701
710
|
}
|
|
711
|
+
mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
|
|
702
712
|
}
|
|
703
713
|
}
|
|
704
714
|
}
|
|
@@ -882,7 +892,7 @@ export async function readToolModuleItems(root, options = {}) {
|
|
|
882
892
|
const records = [];
|
|
883
893
|
for (const filePath of files) {
|
|
884
894
|
const sourceText = await readFile(filePath, "utf8");
|
|
885
|
-
const packageRoot =
|
|
895
|
+
const packageRoot = findToolPackageRoot(filePath);
|
|
886
896
|
const isolatedSourcePath = await resolveIsolatedResourceModulePath(packageRoot, filePath);
|
|
887
897
|
const imported = await import(pathToFileURL(isolatedSourcePath).href);
|
|
888
898
|
const definitions = discoverToolModuleDefinitions(sourceText, imported);
|
|
@@ -958,7 +968,7 @@ export async function loadWorkspaceObjects(workspaceRoot, options = {}) {
|
|
|
958
968
|
await loadConfigYamlForRoot(root, configRoot, mergedAgents, mergedObjects);
|
|
959
969
|
await loadModuleAgentsForRoot(root, mergedAgents);
|
|
960
970
|
if (root !== defaultRoot) {
|
|
961
|
-
await loadConventionalObjectsForRoot(root, mergedObjects, getMergedToolModuleDiscoveryScope(mergedObjects));
|
|
971
|
+
await loadConventionalObjectsForRoot(root, workspaceRoot, mergedObjects, getMergedToolModuleDiscoveryScope(mergedObjects));
|
|
962
972
|
}
|
|
963
973
|
await loadModuleObjectsForRoot(root, mergedObjects);
|
|
964
974
|
await loadRootObjects(root, mergedObjects);
|
|
@@ -302,9 +302,9 @@ export function parseToolObject(object) {
|
|
|
302
302
|
...(mcpServerConfig && Object.keys(mcpServerConfig).length > 0 ? { mcpServer: mcpServerConfig } : {}),
|
|
303
303
|
}
|
|
304
304
|
: undefined),
|
|
305
|
-
subprocess: value.subprocess === true,
|
|
305
|
+
subprocess: value.subprocess === true ? true : undefined,
|
|
306
306
|
inputSchemaRef: typeof asObject(value.inputSchema)?.ref === "string" ? String(asObject(value.inputSchema)?.ref) : undefined,
|
|
307
|
-
hasModuleSchema: value.hasModuleSchema === true,
|
|
307
|
+
hasModuleSchema: value.hasModuleSchema === true ? true : undefined,
|
|
308
308
|
modelSchema: asObject(value.modelSchema),
|
|
309
309
|
embeddingModelRef: typeof value.embeddingModelRef === "string"
|
|
310
310
|
? value.embeddingModelRef
|
|
@@ -325,7 +325,7 @@ export function parseToolObject(object) {
|
|
|
325
325
|
: undefined,
|
|
326
326
|
bundleRefs,
|
|
327
327
|
hitl: parseHitlPolicy(value.hitl),
|
|
328
|
-
retryable: value.retryable === true,
|
|
328
|
+
retryable: value.retryable === true ? true : undefined,
|
|
329
329
|
sourcePath: object.sourcePath,
|
|
330
330
|
};
|
|
331
331
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const DEFAULT_TOOL_SOURCE_URIS: string[];
|
|
2
|
+
export declare const DEFAULT_SKILL_SOURCE_URIS: string[];
|
|
3
|
+
export type RuntimeSourceConfig = {
|
|
4
|
+
tools: string[];
|
|
5
|
+
skills: string[];
|
|
6
|
+
};
|
|
7
|
+
type ToolPackageInstallation = {
|
|
8
|
+
entryPath: string;
|
|
9
|
+
packageRoot: string;
|
|
10
|
+
packageName: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function readRuntimeSources(runtimeDefaults: Record<string, unknown> | undefined): RuntimeSourceConfig;
|
|
13
|
+
export declare function isFileSourceUri(value: string): boolean;
|
|
14
|
+
export declare function isNpmSourceUri(value: string): boolean;
|
|
15
|
+
export declare function isHttpSourceUri(value: string): boolean;
|
|
16
|
+
export declare function resolveFileSourcePath(uri: string, workspaceRoot: string): string;
|
|
17
|
+
export declare function ensureToolPackageSource(uri: string, workspaceRoot: string): Promise<ToolPackageInstallation>;
|
|
18
|
+
export declare function ensureRemoteSkillSource(uri: string): Promise<string>;
|
|
19
|
+
export {};
|