@gt-fe/eap-runtime 0.0.12 → 0.0.14
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 +3 -1
- package/dist/api/handlers/invoke.handler.d.ts +1 -1
- package/dist/api/handlers/invoke.handler.js +3 -0
- package/dist/api/handlers/stream.handler.d.ts +1 -1
- package/dist/api/handlers/stream.handler.js +4 -1
- package/dist/api/routes/agent.routes.d.ts +2 -2
- package/dist/common/index.d.ts +1 -0
- package/dist/common/index.js +1 -0
- package/dist/common/logger.d.ts +16 -4
- package/dist/common/logger.js +82 -16
- package/dist/common/runtime-package-version.d.ts +2 -0
- package/dist/common/runtime-package-version.js +5 -0
- package/dist/common/types.d.ts +5 -5
- package/dist/common/types.js +2 -2
- package/dist/contracts/permission-checker.d.ts +1 -1
- package/dist/debug/llm-turn-log.d.ts +12 -0
- package/dist/debug/llm-turn-log.js +1 -0
- package/dist/governance/governance-client.d.ts +1 -1
- package/dist/governance/index.d.ts +1 -1
- package/dist/governance/permission-guard.d.ts +1 -1
- package/dist/graph/adapters/langgraph.adapter.d.ts +3 -1
- package/dist/graph/adapters/langgraph.adapter.js +67 -14
- package/dist/graph/adapters/subgraph-compiler.js +5 -2
- package/dist/graph/contracts/executable-graph.d.ts +19 -8
- package/dist/graph/contracts/executable-graph.js +1 -1
- package/dist/graph/nodes/llm-node.d.ts +3 -0
- package/dist/graph/nodes/llm-node.js +8 -0
- package/dist/graph/nodes/skill-bind-node.d.ts +3 -4
- package/dist/graph/nodes/skill-bind-node.js +1 -20
- package/dist/graph/nodes/tool-node.d.ts +2 -0
- package/dist/graph/nodes/tool-node.js +20 -14
- package/dist/index.d.ts +1 -1
- package/dist/memory/compression/index.d.ts +1 -1
- package/dist/memory/compression/index.js +1 -1
- package/dist/memory/compression/sliding-window.compression.js +7 -7
- package/dist/memory/index.d.ts +1 -1
- package/dist/memory/index.js +1 -1
- package/dist/memory/message-compression.service.d.ts +12 -1
- package/dist/memory/message-compression.service.js +29 -1
- package/dist/providers/http-llm-provider.d.ts +1 -0
- package/dist/providers/http-llm-provider.js +4 -1
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.js +1 -1
- package/dist/providers/local-llm-provider.d.ts +4 -1
- package/dist/providers/local-llm-provider.js +15 -3
- package/dist/providers/routing-llm-provider.js +13 -4
- package/dist/registry/agent-models.d.ts +1 -1
- package/dist/registry/agent-models.js +1 -1
- package/dist/registry/agent-registry-client.d.ts +1 -1
- package/dist/registry/deployment-snapshot.js +19 -17
- package/dist/registry/graph-loader.d.ts +11 -1
- package/dist/registry/graph-loader.js +115 -56
- package/dist/registry/preload-errors.d.ts +1 -1
- package/dist/registry/preload-errors.js +1 -1
- package/dist/registry/skill-io-guard.js +1 -1
- package/dist/registry/skill-tree.js +1 -1
- package/dist/runtime/agent-runtime.d.ts +3 -3
- package/dist/runtime/agent-runtime.js +14 -4
- package/dist/runtime/dev-runtime.d.ts +2 -0
- package/dist/runtime/dev-runtime.js +2 -0
- package/dist/runtime/types.d.ts +4 -4
- package/dist/server.js +1 -1
- package/dist/session/compression-middleware.d.ts +2 -1
- package/dist/session/compression-middleware.js +7 -4
- package/dist/sse/events/skill-events.d.ts +1 -1
- package/dist/sse/events/skill-events.js +1 -1
- package/dist/sse/events/stats-events.d.ts +9 -0
- package/dist/token/compression-budget-integration.d.ts +1 -1
- package/dist/token/compression-budget-integration.js +18 -10
- package/dist/token/context-window.d.ts +42 -0
- package/dist/token/context-window.js +73 -0
- package/dist/token/index.d.ts +1 -0
- package/dist/token/index.js +1 -0
- package/dist/tool/create-tool-executor.d.ts +1 -1
- package/dist/tool/dev-http-executor.d.ts +2 -1
- package/dist/tool/dev-http-executor.js +14 -2
- package/dist/tool/dev-mcp-executor.d.ts +11 -1
- package/dist/tool/dev-mcp-executor.js +27 -1
- package/dist/tool/dev-tool-executor.d.ts +2 -1
- package/dist/tool/dev-tool-executor.js +6 -0
- package/dist/tool/harness-command.js +1 -1
- package/dist/tool/harness-primitives.d.ts +7 -0
- package/dist/tool/harness-primitives.js +57 -6
- package/dist/tool/http-tool-executor.js +1 -1
- package/dist/tool/index.d.ts +2 -1
- package/dist/tool/index.js +1 -0
- package/dist/tool/local-mcp-client.d.ts +5 -2
- package/dist/tool/local-mcp-client.js +130 -3
- package/dist/tool/local-tool-executor.d.ts +3 -2
- package/dist/tool/local-tool-executor.js +16 -1
- package/dist/tool/mcp-list.d.ts +53 -0
- package/dist/tool/mcp-list.js +199 -0
- package/dist/tool/mcp-tool-executor.d.ts +6 -1
- package/dist/tool/mcp-tool-executor.js +53 -1
- package/dist/tool/skill-dir-infer.d.ts +17 -0
- package/dist/tool/skill-dir-infer.js +81 -0
- package/dist/tool/skill-script-refs.d.ts +1 -8
- package/dist/tool/skill-script-refs.js +32 -20
- package/dist/tool/system-prompt-builder.d.ts +35 -16
- package/dist/tool/system-prompt-builder.js +30 -58
- package/dist/tool/timeout.d.ts +2 -2
- package/dist/tool/timeout.js +6 -3
- package/dist/tool/tool-execution-router.d.ts +3 -2
- package/dist/tool/tool-execution-router.js +8 -1
- package/dist/tool/tool-executor.interface.d.ts +11 -0
- package/dist/tool/tool-orchestrator.d.ts +1 -1
- package/dist/tool/tool-orchestrator.js +1 -1
- package/dist/utils/format-error.d.ts +2 -2
- package/dist/utils/format-error.js +2 -2
- package/dist/utils/http-error.d.ts +2 -2
- package/dist/utils/http-error.js +2 -2
- package/package.json +6 -33
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - 其余 → NOT_CONFIGURED
|
|
11
11
|
*/
|
|
12
12
|
import { DevMcpExecutor } from './dev-mcp-executor.js';
|
|
13
|
-
import type { IToolExecutor, ToolCallRequest, ToolCallResult } from './tool-executor.interface.js';
|
|
13
|
+
import type { IToolExecutor, McpListedTool, ToolCallRequest, ToolCallResult } from './tool-executor.interface.js';
|
|
14
14
|
export interface DevToolExecutorOptions {
|
|
15
15
|
mcp?: ConstructorParameters<typeof DevMcpExecutor>[0];
|
|
16
16
|
}
|
|
@@ -20,4 +20,5 @@ export declare class DevToolExecutor implements IToolExecutor {
|
|
|
20
20
|
private readonly local;
|
|
21
21
|
constructor(options?: DevToolExecutorOptions);
|
|
22
22
|
execute(request: ToolCallRequest): Promise<ToolCallResult>;
|
|
23
|
+
listTools(request: ToolCallRequest): Promise<McpListedTool[]>;
|
|
23
24
|
}
|
|
@@ -47,6 +47,12 @@ export class DevToolExecutor {
|
|
|
47
47
|
runtime: 'local',
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
+
async listTools(request) {
|
|
51
|
+
if (hasMcpExecution(request.execution)) {
|
|
52
|
+
return this.mcp.listTools(request);
|
|
53
|
+
}
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
50
56
|
}
|
|
51
57
|
function hasMcpExecution(raw) {
|
|
52
58
|
if (!raw || typeof raw !== 'object')
|
|
@@ -21,7 +21,7 @@ export function commandDialectLines(platform = process.platform) {
|
|
|
21
21
|
else {
|
|
22
22
|
lines.push('基础命令:当前目录 `pwd`;列目录 `ls`;环境变量 `$VAR`;串命令用 `&&`。');
|
|
23
23
|
}
|
|
24
|
-
lines.push('解释器:`python`/`python3`、`node`/`nodejs`、`sh`/`bash`(Windows 上 `sh` 需本机有 Git Bash 等)。', '可执行文件:用绝对路径或相对于 cwd(Windows `.exe`/`.bat`/`.cmd`;Unix 二进制需可执行或用 `sh`)。', '`execute_command` 的 cwd 是用户目录。技能包只读。包内任意文件用 `EAP_SKILL_DIR` 加相对路径(不限 `scripts/`)。读到的 SKILL.md
|
|
24
|
+
lines.push('解释器:`python`/`python3`、`node`/`nodejs`、`sh`/`bash`(Windows 上 `sh` 需本机有 Git Bash 等)。', '可执行文件:用绝对路径或相对于 cwd(Windows `.exe`/`.bat`/`.cmd`;Unix 二进制需可执行或用 `sh`)。', '`execute_command` 的 cwd 是用户目录。技能包只读。包内任意文件用 `EAP_SKILL_DIR` 加相对路径(不限 `scripts/`)。读到的 SKILL.md 里本包存在的相对路径已展开为绝对路径。不要把 cwd 设成技能目录。相对写出在用户目录。');
|
|
25
25
|
return lines;
|
|
26
26
|
}
|
|
27
27
|
export function resolveShellSpawn(command, platform = process.platform) {
|
|
@@ -34,6 +34,13 @@ export declare function resolveJailFromState(state: {
|
|
|
34
34
|
export declare function resolveJailRoots(options?: HarnessJailOptions): string[];
|
|
35
35
|
/** 路径落在写根外的 extraRoot 时,返回该 extraRoot。 */
|
|
36
36
|
export declare function extraRootContaining(abs: string, extraRoots: string[], workdir: string): string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* 相对 read_file:`技能名/文件`、全库唯一命中、显式 skillDir,否则用户目录。
|
|
39
|
+
*/
|
|
40
|
+
export declare function locateSkillRelativeRead(filePath: string, workdir: string | undefined, skillDir: string | undefined, extraRoots?: string[]): {
|
|
41
|
+
path: string;
|
|
42
|
+
relativeBase: string | undefined;
|
|
43
|
+
};
|
|
37
44
|
export declare function resolveJailedPath(requested: string, roots: string[], opts?: {
|
|
38
45
|
mustExist?: boolean;
|
|
39
46
|
relativeBase?: string;
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* 开发态可选 overlay:`EAP_LOCAL_SKILLS_DIR` / `EAP_LOCAL_TOOLS_DIR`(只读)。
|
|
9
9
|
*/
|
|
10
10
|
import { spawn } from 'node:child_process';
|
|
11
|
-
import { constants as fsConstants, realpathSync } from 'node:fs';
|
|
11
|
+
import { constants as fsConstants, existsSync, realpathSync } from 'node:fs';
|
|
12
12
|
import { mkdir, open, readFile } from 'node:fs/promises';
|
|
13
13
|
import path from 'node:path';
|
|
14
|
-
import { BUILTIN_TOOL_SOURCE, DEFAULT_BUILTIN_TIMEOUT_MS, MAX_BUILTIN_TIMEOUT_MS, clampBuiltinTimeout, isBuiltinToolExecution, resolveBuiltinToolsConfig, } from '@
|
|
14
|
+
import { BUILTIN_TOOL_SOURCE, DEFAULT_BUILTIN_TIMEOUT_MS, MAX_BUILTIN_TIMEOUT_MS, clampBuiltinTimeout, isBuiltinToolExecution, resolveBuiltinToolsConfig, } from '@eap/contracts';
|
|
15
15
|
import { ensureInterpreterShims, formatSpawnError, killProcessTree, resolveShellSpawn, sandboxEnv, spawnOptionsFor, substituteSkillDir, } from './harness-command.js';
|
|
16
16
|
import { expandSkillScriptRefs, isSkillEntryFile } from './skill-script-refs.js';
|
|
17
17
|
function builtinConfig() {
|
|
@@ -252,6 +252,53 @@ function resolveViaExistingAncestor(abs) {
|
|
|
252
252
|
cur = parent;
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
|
+
function userReadBase(workdir) {
|
|
256
|
+
return workdir?.trim()
|
|
257
|
+
? (tryRealpath(path.resolve(workdir)) ?? path.resolve(workdir))
|
|
258
|
+
: undefined;
|
|
259
|
+
}
|
|
260
|
+
function skillPackageRoots(roots) {
|
|
261
|
+
const out = [];
|
|
262
|
+
const seen = new Set();
|
|
263
|
+
for (const root of roots) {
|
|
264
|
+
const resolved = path.resolve(root);
|
|
265
|
+
if (seen.has(resolved))
|
|
266
|
+
continue;
|
|
267
|
+
seen.add(resolved);
|
|
268
|
+
if (existsSync(path.join(resolved, 'SKILL.md')) || existsSync(path.join(resolved, 'skill.md'))) {
|
|
269
|
+
out.push(resolved);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return out;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* 相对 read_file:`技能名/文件`、全库唯一命中、显式 skillDir,否则用户目录。
|
|
276
|
+
*/
|
|
277
|
+
export function locateSkillRelativeRead(filePath, workdir, skillDir, extraRoots) {
|
|
278
|
+
const trimmed = filePath.trim();
|
|
279
|
+
const userBase = userReadBase(workdir);
|
|
280
|
+
if (!trimmed || path.isAbsolute(trimmed))
|
|
281
|
+
return { path: trimmed, relativeBase: userBase };
|
|
282
|
+
const packages = skillPackageRoots([
|
|
283
|
+
...(skillDir?.trim() ? [skillDir] : []),
|
|
284
|
+
...(extraRoots ?? []),
|
|
285
|
+
]);
|
|
286
|
+
const slash = trimmed.replace(/\\/g, '/');
|
|
287
|
+
const segs = slash.split('/').filter(Boolean);
|
|
288
|
+
if (segs.length >= 2) {
|
|
289
|
+
const hit = packages.find((pkg) => path.basename(pkg) === segs[0]);
|
|
290
|
+
if (hit)
|
|
291
|
+
return { path: segs.slice(1).join('/'), relativeBase: hit };
|
|
292
|
+
}
|
|
293
|
+
const matches = packages.filter((pkg) => existsSync(path.resolve(pkg, trimmed)));
|
|
294
|
+
if (matches.length === 1)
|
|
295
|
+
return { path: trimmed, relativeBase: matches[0] };
|
|
296
|
+
const skillBase = skillDir?.trim() ? path.resolve(skillDir) : undefined;
|
|
297
|
+
if (skillBase && existsSync(path.resolve(skillBase, trimmed))) {
|
|
298
|
+
return { path: trimmed, relativeBase: skillBase };
|
|
299
|
+
}
|
|
300
|
+
return { path: trimmed, relativeBase: userBase };
|
|
301
|
+
}
|
|
255
302
|
export function resolveJailedPath(requested, roots, opts) {
|
|
256
303
|
const trimmed = requested.trim();
|
|
257
304
|
if (!trimmed || trimmed.includes('\0')) {
|
|
@@ -344,10 +391,10 @@ export class HarnessPrimitiveExecutor {
|
|
|
344
391
|
async readFile(request) {
|
|
345
392
|
const filePath = String(request.arguments.path ?? request.arguments.file ?? '');
|
|
346
393
|
const workdir = this.workdirFor(request);
|
|
347
|
-
const
|
|
348
|
-
const target = resolveJailedPath(
|
|
394
|
+
const located = locateSkillRelativeRead(filePath, workdir, request.skillDir, this.extraRootsFor(request));
|
|
395
|
+
const target = resolveJailedPath(located.path, this.rootsFor(request), {
|
|
349
396
|
mustExist: true,
|
|
350
|
-
relativeBase,
|
|
397
|
+
relativeBase: located.relativeBase,
|
|
351
398
|
});
|
|
352
399
|
const buf = await readFile(target);
|
|
353
400
|
const config = builtinConfig();
|
|
@@ -481,7 +528,7 @@ export class HarnessPrimitiveExecutor {
|
|
|
481
528
|
}
|
|
482
529
|
export function withHarnessPrimitives(inner, options) {
|
|
483
530
|
const harness = new HarnessPrimitiveExecutor(options);
|
|
484
|
-
|
|
531
|
+
const wrapped = {
|
|
485
532
|
execute(request) {
|
|
486
533
|
if (isBuiltinToolExecution(request.execution)) {
|
|
487
534
|
return harness.execute(request);
|
|
@@ -489,4 +536,8 @@ export function withHarnessPrimitives(inner, options) {
|
|
|
489
536
|
return inner.execute(request);
|
|
490
537
|
},
|
|
491
538
|
};
|
|
539
|
+
if (typeof inner.listTools === 'function') {
|
|
540
|
+
wrapped.listTools = (request) => inner.listTools(request);
|
|
541
|
+
}
|
|
542
|
+
return wrapped;
|
|
492
543
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @see docs/contracts/tool-execution.md §2
|
|
5
5
|
* @see docs/contracts/cross-service-api.md §4.6
|
|
6
6
|
*/
|
|
7
|
-
import { ErrorCodes } from '@
|
|
7
|
+
import { ErrorCodes } from '@eap/contracts';
|
|
8
8
|
import { abortOrTimeoutMessage, isAbortError } from './timeout.js';
|
|
9
9
|
function mapGatewayHttpError(status, body, statusText) {
|
|
10
10
|
const code = body?.error?.code;
|
package/dist/tool/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export type { AuthContext, ToolCallRequest, ToolCallResult, LLMToolCall, LLMToolCallResponse, IToolExecutor, } from './tool-executor.interface.js';
|
|
1
|
+
export type { AuthContext, ToolCallRequest, ToolCallResult, LLMToolCall, LLMToolCallResponse, IToolExecutor, McpListedTool, } from './tool-executor.interface.js';
|
|
2
|
+
export { hydrateMcpTools, isMcpDiscoverStub, needsMcpHydration, parseListedTools, } from './mcp-list.js';
|
|
2
3
|
export { toLoadedToolMap, type LoadedToolDefinition, type LoadedToolMap, type LoadedToolManifestRef, } from './loaded-tool-definition.js';
|
|
3
4
|
export { ToolOrchestrator } from './tool-orchestrator.js';
|
|
4
5
|
export { HttpToolExecutor, type HttpToolExecutorOptions, } from './http-tool-executor.js';
|
package/dist/tool/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { hydrateMcpTools, isMcpDiscoverStub, needsMcpHydration, parseListedTools, } from './mcp-list.js';
|
|
1
2
|
export { toLoadedToolMap, } from './loaded-tool-definition.js';
|
|
2
3
|
export { ToolOrchestrator } from './tool-orchestrator.js';
|
|
3
4
|
export { HttpToolExecutor, } from './http-tool-executor.js';
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* LocalMcpClient — 桌面本地 MCP 调用(stdio / streamable_http)
|
|
3
3
|
*
|
|
4
4
|
* mcp-data-service 等上游要求 HTTP 头 `userId`(或 query),再注入 `params._meta.userId`。
|
|
5
|
-
* 身份优先来自 Runtime ExecutionContext(Token.sub);manifest
|
|
5
|
+
* 身份优先来自 Runtime ExecutionContext(Token.sub);manifest header.userId 作兜底。
|
|
6
6
|
*/
|
|
7
|
-
import type { McpRuntimeExecution } from '@
|
|
7
|
+
import type { McpRuntimeExecution } from '@eap/contracts';
|
|
8
8
|
import { type FileArtifact } from './file-artifact.js';
|
|
9
|
+
import type { McpListedTool } from './tool-executor.interface.js';
|
|
9
10
|
export interface LocalMcpInvokeContext {
|
|
10
11
|
/** Platform Token.sub / ExecutionContext.userId */
|
|
11
12
|
userId?: string | null;
|
|
@@ -16,8 +17,10 @@ export interface LocalMcpInvokeResult {
|
|
|
16
17
|
}
|
|
17
18
|
export interface ILocalMcpClient {
|
|
18
19
|
invoke(execution: McpRuntimeExecution, args: Record<string, unknown>, context?: LocalMcpInvokeContext): Promise<LocalMcpInvokeResult>;
|
|
20
|
+
list(execution: McpRuntimeExecution, context?: LocalMcpInvokeContext): Promise<McpListedTool[]>;
|
|
19
21
|
}
|
|
20
22
|
export declare function formatMcpOutput(result: unknown): string;
|
|
21
23
|
export declare class LocalMcpClient implements ILocalMcpClient {
|
|
22
24
|
invoke(execution: McpRuntimeExecution, args: Record<string, unknown>, context?: LocalMcpInvokeContext): Promise<LocalMcpInvokeResult>;
|
|
25
|
+
list(execution: McpRuntimeExecution, context?: LocalMcpInvokeContext): Promise<McpListedTool[]>;
|
|
23
26
|
}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* LocalMcpClient — 桌面本地 MCP 调用(stdio / streamable_http)
|
|
3
3
|
*
|
|
4
4
|
* mcp-data-service 等上游要求 HTTP 头 `userId`(或 query),再注入 `params._meta.userId`。
|
|
5
|
-
* 身份优先来自 Runtime ExecutionContext(Token.sub);manifest
|
|
5
|
+
* 身份优先来自 Runtime ExecutionContext(Token.sub);manifest header.userId 作兜底。
|
|
6
6
|
*/
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import readline from 'node:readline';
|
|
9
9
|
import { isAbortError, resolveToolTimeoutSec, toToolTimeoutMs, toolTimeoutErrorMessage, } from './timeout.js';
|
|
10
10
|
import { fileArtifactFromToolOutput, fileBasesFromEnv } from './file-artifact.js';
|
|
11
|
+
import { nextListCursor, parseListedTools } from './mcp-list.js';
|
|
11
12
|
export function formatMcpOutput(result) {
|
|
12
13
|
const r = result;
|
|
13
14
|
if (r?.structuredContent !== undefined) {
|
|
@@ -53,7 +54,7 @@ function expandEnvRefs(value) {
|
|
|
53
54
|
return value.replace(/\$\{([A-Z][A-Z0-9_]*)\}/g, (_match, name) => process.env[name] ?? '');
|
|
54
55
|
}
|
|
55
56
|
function resolveUserId(_execution, context) {
|
|
56
|
-
// 仅用调用期身份(Token/ExecutionContext);禁止依赖 manifest 静态
|
|
57
|
+
// 仅用调用期身份(Token/ExecutionContext);禁止依赖 manifest 静态 header.userId
|
|
57
58
|
const fromCtx = context?.userId?.trim();
|
|
58
59
|
return fromCtx || undefined;
|
|
59
60
|
}
|
|
@@ -142,6 +143,61 @@ async function invokeStdio(execution, args, context) {
|
|
|
142
143
|
rl.close();
|
|
143
144
|
}
|
|
144
145
|
}
|
|
146
|
+
async function listStdio(execution, context) {
|
|
147
|
+
const { server } = execution;
|
|
148
|
+
const command = server.command ?? 'node';
|
|
149
|
+
const spawnArgs = server.args ?? [];
|
|
150
|
+
const timeoutMs = toToolTimeoutMs(execution.timeout);
|
|
151
|
+
const userId = resolveUserId(execution, context);
|
|
152
|
+
const child = spawn(command, spawnArgs, {
|
|
153
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
154
|
+
cwd: server.cwd,
|
|
155
|
+
env: {
|
|
156
|
+
...process.env,
|
|
157
|
+
...server.env,
|
|
158
|
+
...(userId ? { EAP_MCP_USER_ID: userId } : {}),
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
const session = {
|
|
162
|
+
process: child,
|
|
163
|
+
nextId: 1,
|
|
164
|
+
pending: new Map(),
|
|
165
|
+
timeoutMs,
|
|
166
|
+
};
|
|
167
|
+
const rl = readline.createInterface({ input: child.stdout });
|
|
168
|
+
rl.on('line', (line) => {
|
|
169
|
+
if (!line.trim())
|
|
170
|
+
return;
|
|
171
|
+
try {
|
|
172
|
+
const msg = JSON.parse(line);
|
|
173
|
+
if (msg.id == null)
|
|
174
|
+
return;
|
|
175
|
+
const p = session.pending.get(msg.id);
|
|
176
|
+
if (!p)
|
|
177
|
+
return;
|
|
178
|
+
session.pending.delete(msg.id);
|
|
179
|
+
if (msg.error)
|
|
180
|
+
p.reject(new Error(msg.error.message));
|
|
181
|
+
else
|
|
182
|
+
p.resolve(msg.result);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
/* ignore */
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
try {
|
|
189
|
+
await rpcStdio(session, 'initialize', {
|
|
190
|
+
protocolVersion: '2024-11-05',
|
|
191
|
+
capabilities: {},
|
|
192
|
+
clientInfo: { name: 'eap-runtime-local', version: '0.1.0' },
|
|
193
|
+
});
|
|
194
|
+
return await collectListedTools((params) => rpcStdio(session, 'tools/list', params));
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
child.kill();
|
|
198
|
+
rl.close();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
145
201
|
async function postMcp(url, headers, payload, signal) {
|
|
146
202
|
const response = await fetch(url, {
|
|
147
203
|
method: 'POST',
|
|
@@ -171,7 +227,7 @@ async function invokeStreamableHttp(execution, args, context) {
|
|
|
171
227
|
const controller = new AbortController();
|
|
172
228
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
173
229
|
const userId = resolveUserId(execution, context);
|
|
174
|
-
const rawHeaders = execution.server.
|
|
230
|
+
const rawHeaders = execution.server.header ?? {};
|
|
175
231
|
const expandedHeaders = {};
|
|
176
232
|
for (const [key, value] of Object.entries(rawHeaders)) {
|
|
177
233
|
expandedHeaders[key] = expandEnvRefs(value);
|
|
@@ -219,6 +275,61 @@ async function invokeStreamableHttp(execution, args, context) {
|
|
|
219
275
|
clearTimeout(timer);
|
|
220
276
|
}
|
|
221
277
|
}
|
|
278
|
+
async function listStreamableHttp(execution, context) {
|
|
279
|
+
const url = expandEnvRefs(execution.server.url);
|
|
280
|
+
if (!url) {
|
|
281
|
+
throw new Error('streamable_http MCP requires server.url');
|
|
282
|
+
}
|
|
283
|
+
const timeoutSec = resolveToolTimeoutSec(execution.timeout);
|
|
284
|
+
const timeoutMs = toToolTimeoutMs(timeoutSec);
|
|
285
|
+
const controller = new AbortController();
|
|
286
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
287
|
+
const userId = resolveUserId(execution, context);
|
|
288
|
+
const rawHeaders = execution.server.header ?? {};
|
|
289
|
+
const expandedHeaders = {};
|
|
290
|
+
for (const [key, value] of Object.entries(rawHeaders)) {
|
|
291
|
+
expandedHeaders[key] = expandEnvRefs(value);
|
|
292
|
+
}
|
|
293
|
+
const headers = {
|
|
294
|
+
'Content-Type': 'application/json',
|
|
295
|
+
Accept: 'application/json, text/event-stream',
|
|
296
|
+
...expandedHeaders,
|
|
297
|
+
};
|
|
298
|
+
if (userId) {
|
|
299
|
+
headers.userId = userId;
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
const init = await postMcp(url, headers, {
|
|
303
|
+
jsonrpc: '2.0',
|
|
304
|
+
id: 1,
|
|
305
|
+
method: 'initialize',
|
|
306
|
+
params: {
|
|
307
|
+
protocolVersion: '2024-11-05',
|
|
308
|
+
capabilities: {},
|
|
309
|
+
clientInfo: { name: 'eap-runtime-local', version: '0.1.0' },
|
|
310
|
+
},
|
|
311
|
+
}, controller.signal);
|
|
312
|
+
const listHeaders = { ...headers };
|
|
313
|
+
if (init.sessionId)
|
|
314
|
+
listHeaders['mcp-session-id'] = init.sessionId;
|
|
315
|
+
let nextId = 2;
|
|
316
|
+
return await collectListedTools(async (params) => {
|
|
317
|
+
const listed = await postMcp(url, listHeaders, { jsonrpc: '2.0', id: nextId++, method: 'tools/list', params }, controller.signal);
|
|
318
|
+
return listed.body.result;
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
if (isAbortError(err)) {
|
|
323
|
+
const timeoutErr = new Error(toolTimeoutErrorMessage(timeoutSec));
|
|
324
|
+
timeoutErr.name = 'TimeoutError';
|
|
325
|
+
throw timeoutErr;
|
|
326
|
+
}
|
|
327
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
328
|
+
}
|
|
329
|
+
finally {
|
|
330
|
+
clearTimeout(timer);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
222
333
|
function parseMcpHttpBody(raw) {
|
|
223
334
|
const trimmed = raw.trim();
|
|
224
335
|
if (trimmed.startsWith('{')) {
|
|
@@ -244,6 +355,22 @@ export class LocalMcpClient {
|
|
|
244
355
|
}
|
|
245
356
|
return invokeStdio(execution, args, context);
|
|
246
357
|
}
|
|
358
|
+
async list(execution, context) {
|
|
359
|
+
if (execution.transport === 'streamable_http') {
|
|
360
|
+
return listStreamableHttp(execution, context);
|
|
361
|
+
}
|
|
362
|
+
return listStdio(execution, context);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
async function collectListedTools(rpcList) {
|
|
366
|
+
const tools = [];
|
|
367
|
+
let cursor;
|
|
368
|
+
do {
|
|
369
|
+
const result = await rpcList(cursor ? { cursor } : {});
|
|
370
|
+
tools.push(...parseListedTools(result));
|
|
371
|
+
cursor = nextListCursor(result);
|
|
372
|
+
} while (cursor);
|
|
373
|
+
return tools;
|
|
247
374
|
}
|
|
248
375
|
function toInvokeResult(raw, serverEnv) {
|
|
249
376
|
const structured = (raw && typeof raw === 'object' && !Array.isArray(raw))
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* 优先 ILocalToolInstallStore;若无安装记录但 request.execution 带
|
|
5
5
|
* 或 request.execution 带可解析的 code bundlePath+handler,则直接本机执行。
|
|
6
6
|
*/
|
|
7
|
-
import type { ILocalToolInstallStore } from '@
|
|
8
|
-
import type { IToolExecutor, ToolCallRequest, ToolCallResult } from './tool-executor.interface.js';
|
|
7
|
+
import type { ILocalToolInstallStore } from '@eap/contracts';
|
|
8
|
+
import type { IToolExecutor, McpListedTool, ToolCallRequest, ToolCallResult } from './tool-executor.interface.js';
|
|
9
9
|
import { type ILocalMcpClient } from './local-mcp-client.js';
|
|
10
10
|
export interface LocalToolExecutorOptions {
|
|
11
11
|
installStore: ILocalToolInstallStore;
|
|
@@ -30,6 +30,7 @@ export declare class LocalToolExecutor implements IToolExecutor {
|
|
|
30
30
|
private readonly mcpClient;
|
|
31
31
|
constructor(options: LocalToolExecutorOptions);
|
|
32
32
|
execute(request: ToolCallRequest): Promise<ToolCallResult>;
|
|
33
|
+
listTools(request: ToolCallRequest): Promise<McpListedTool[]>;
|
|
33
34
|
private executeCodeSpec;
|
|
34
35
|
private executeCode;
|
|
35
36
|
private executeMcp;
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import { existsSync } from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
|
-
import { ErrorCodes, resolveReadyInstallByToolName, resolveMcpExecutionFromInstall, resolveCodeHandlerFromInstall, } from '@
|
|
10
|
+
import { ErrorCodes, resolveReadyInstallByToolName, resolveMcpExecutionFromInstall, resolveCodeHandlerFromInstall, } from '@eap/contracts';
|
|
11
11
|
import { isAbortError, toToolTimeoutMs, toolTimeoutErrorMessage } from './timeout.js';
|
|
12
12
|
import { fileArtifactFromToolOutput, fileBasesFromEnv } from './file-artifact.js';
|
|
13
13
|
import { LocalMcpClient } from './local-mcp-client.js';
|
|
14
|
+
import { normalizeMcpExecution } from './dev-mcp-executor.js';
|
|
14
15
|
/**
|
|
15
16
|
* 根据 execution.runtime 解析执行命令:
|
|
16
17
|
* - node(默认)→ process.execPath + [handlerPath]
|
|
@@ -171,6 +172,20 @@ export class LocalToolExecutor {
|
|
|
171
172
|
runtime: 'local',
|
|
172
173
|
};
|
|
173
174
|
}
|
|
175
|
+
async listTools(request) {
|
|
176
|
+
const fromRequest = normalizeMcpExecution(request.execution, request.toolName);
|
|
177
|
+
if (fromRequest) {
|
|
178
|
+
return this.mcpClient.list(fromRequest, { userId: request.authContext?.userId });
|
|
179
|
+
}
|
|
180
|
+
const install = resolveReadyInstallByToolName(this.options.installStore, request.toolName);
|
|
181
|
+
if (install && install.installStatus === 'ready' && (install.type ?? request.toolType) === 'mcp') {
|
|
182
|
+
const execution = resolveMcpExecutionFromInstall(install);
|
|
183
|
+
if (execution) {
|
|
184
|
+
return this.mcpClient.list(execution, { userId: request.authContext?.userId });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
throw new Error(`MCP tools/list: no mcp execution for "${request.toolName}"`);
|
|
188
|
+
}
|
|
174
189
|
async executeCodeSpec(request, spec) {
|
|
175
190
|
try {
|
|
176
191
|
const stdout = await runHandler(spec.handlerPath, request.arguments, spec.timeoutMs ?? this.options.timeoutMs ?? 30_000, { runtime: spec.runtime, args: spec.args });
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tools/list 解析、discover stub 判定、hydrate。
|
|
3
|
+
*
|
|
4
|
+
* type: mcp 的预装工具集不展开清单 tools[],先产出 discover stub;
|
|
5
|
+
* 有 ExecutionContext 后由同一执行器 tools/list,再写入 LLM tools。
|
|
6
|
+
*/
|
|
7
|
+
import type { LoadedToolDefinition, LoadedToolMap } from './loaded-tool-definition.js';
|
|
8
|
+
import type { AuthContext, IToolExecutor, McpListedTool } from './tool-executor.interface.js';
|
|
9
|
+
export declare function parseListedTools(result: unknown): McpListedTool[];
|
|
10
|
+
export declare function nextListCursor(result: unknown): string | undefined;
|
|
11
|
+
export interface McpDiscoverExecution {
|
|
12
|
+
protocol?: string;
|
|
13
|
+
discover?: boolean;
|
|
14
|
+
transport?: string;
|
|
15
|
+
server?: Record<string, unknown>;
|
|
16
|
+
timeout?: number;
|
|
17
|
+
bundlePath?: string;
|
|
18
|
+
enabledTools?: string[];
|
|
19
|
+
autoApprove?: string[];
|
|
20
|
+
}
|
|
21
|
+
export declare function readMcpDiscoverExecution(raw: unknown): McpDiscoverExecution | null;
|
|
22
|
+
export declare function isMcpDiscoverStub(def: LoadedToolDefinition): boolean;
|
|
23
|
+
export declare function mcpEndpointKey(execution: unknown, fallback?: string): string;
|
|
24
|
+
export declare const MCP_LIST_TOOL_NAME = "tools/list";
|
|
25
|
+
/** 展示用:`tl-data-035/get_dataset_by_code`;无名工具集则只返回工具名。 */
|
|
26
|
+
export declare function formatToolsetToolName(toolset: string | undefined, toolName: string): string;
|
|
27
|
+
/** 从 hydrate 后的 toolId(`toolset:toolName`)取出工具集编码。 */
|
|
28
|
+
export declare function toolsetCodeFromLoaded(loaded: {
|
|
29
|
+
toolId?: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
manifest?: {
|
|
32
|
+
id?: string;
|
|
33
|
+
};
|
|
34
|
+
} | undefined): string | undefined;
|
|
35
|
+
export interface HydrateMcpToolsAuth {
|
|
36
|
+
authContext: AuthContext;
|
|
37
|
+
correlationId?: string;
|
|
38
|
+
taskId?: string;
|
|
39
|
+
onListFailure?: (failure: {
|
|
40
|
+
toolset: string;
|
|
41
|
+
endpoint: string;
|
|
42
|
+
error: string;
|
|
43
|
+
}) => void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 将 MCP discover stub 替换为 tools/list 结果。
|
|
47
|
+
* 无 stub 时原样返回(同一引用)。按 endpoint+user+agent 在执行器内缓存 list。
|
|
48
|
+
* enabledTools 为可选白名单;autoApprove 的子工具 requireConfirmation=false。
|
|
49
|
+
* 单个 endpoint 的 list 失败只跳过该工具集;全部失败才抛错(错误含工具集名)。
|
|
50
|
+
*/
|
|
51
|
+
export declare function hydrateMcpTools(loadedTools: LoadedToolMap | undefined, executor: IToolExecutor | undefined, auth: HydrateMcpToolsAuth): Promise<LoadedToolMap | undefined>;
|
|
52
|
+
/** 测试与 HITL 续跑:从已 hydrate 的 map 判断是否还要 list */
|
|
53
|
+
export declare function needsMcpHydration(loadedTools: LoadedToolMap | undefined): boolean;
|