@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.
Files changed (112) hide show
  1. package/README.md +3 -1
  2. package/dist/api/handlers/invoke.handler.d.ts +1 -1
  3. package/dist/api/handlers/invoke.handler.js +3 -0
  4. package/dist/api/handlers/stream.handler.d.ts +1 -1
  5. package/dist/api/handlers/stream.handler.js +4 -1
  6. package/dist/api/routes/agent.routes.d.ts +2 -2
  7. package/dist/common/index.d.ts +1 -0
  8. package/dist/common/index.js +1 -0
  9. package/dist/common/logger.d.ts +16 -4
  10. package/dist/common/logger.js +82 -16
  11. package/dist/common/runtime-package-version.d.ts +2 -0
  12. package/dist/common/runtime-package-version.js +5 -0
  13. package/dist/common/types.d.ts +5 -5
  14. package/dist/common/types.js +2 -2
  15. package/dist/contracts/permission-checker.d.ts +1 -1
  16. package/dist/debug/llm-turn-log.d.ts +12 -0
  17. package/dist/debug/llm-turn-log.js +1 -0
  18. package/dist/governance/governance-client.d.ts +1 -1
  19. package/dist/governance/index.d.ts +1 -1
  20. package/dist/governance/permission-guard.d.ts +1 -1
  21. package/dist/graph/adapters/langgraph.adapter.d.ts +3 -1
  22. package/dist/graph/adapters/langgraph.adapter.js +67 -14
  23. package/dist/graph/adapters/subgraph-compiler.js +5 -2
  24. package/dist/graph/contracts/executable-graph.d.ts +19 -8
  25. package/dist/graph/contracts/executable-graph.js +1 -1
  26. package/dist/graph/nodes/llm-node.d.ts +3 -0
  27. package/dist/graph/nodes/llm-node.js +8 -0
  28. package/dist/graph/nodes/skill-bind-node.d.ts +3 -4
  29. package/dist/graph/nodes/skill-bind-node.js +1 -20
  30. package/dist/graph/nodes/tool-node.d.ts +2 -0
  31. package/dist/graph/nodes/tool-node.js +20 -14
  32. package/dist/index.d.ts +1 -1
  33. package/dist/memory/compression/index.d.ts +1 -1
  34. package/dist/memory/compression/index.js +1 -1
  35. package/dist/memory/compression/sliding-window.compression.js +7 -7
  36. package/dist/memory/index.d.ts +1 -1
  37. package/dist/memory/index.js +1 -1
  38. package/dist/memory/message-compression.service.d.ts +12 -1
  39. package/dist/memory/message-compression.service.js +29 -1
  40. package/dist/providers/http-llm-provider.d.ts +1 -0
  41. package/dist/providers/http-llm-provider.js +4 -1
  42. package/dist/providers/index.d.ts +1 -1
  43. package/dist/providers/index.js +1 -1
  44. package/dist/providers/local-llm-provider.d.ts +4 -1
  45. package/dist/providers/local-llm-provider.js +15 -3
  46. package/dist/providers/routing-llm-provider.js +13 -4
  47. package/dist/registry/agent-models.d.ts +1 -1
  48. package/dist/registry/agent-models.js +1 -1
  49. package/dist/registry/agent-registry-client.d.ts +1 -1
  50. package/dist/registry/deployment-snapshot.js +19 -17
  51. package/dist/registry/graph-loader.d.ts +11 -1
  52. package/dist/registry/graph-loader.js +115 -56
  53. package/dist/registry/preload-errors.d.ts +1 -1
  54. package/dist/registry/preload-errors.js +1 -1
  55. package/dist/registry/skill-io-guard.js +1 -1
  56. package/dist/registry/skill-tree.js +1 -1
  57. package/dist/runtime/agent-runtime.d.ts +3 -3
  58. package/dist/runtime/agent-runtime.js +14 -4
  59. package/dist/runtime/dev-runtime.d.ts +2 -0
  60. package/dist/runtime/dev-runtime.js +2 -0
  61. package/dist/runtime/types.d.ts +4 -4
  62. package/dist/server.js +1 -1
  63. package/dist/session/compression-middleware.d.ts +2 -1
  64. package/dist/session/compression-middleware.js +7 -4
  65. package/dist/sse/events/skill-events.d.ts +1 -1
  66. package/dist/sse/events/skill-events.js +1 -1
  67. package/dist/sse/events/stats-events.d.ts +9 -0
  68. package/dist/token/compression-budget-integration.d.ts +1 -1
  69. package/dist/token/compression-budget-integration.js +18 -10
  70. package/dist/token/context-window.d.ts +42 -0
  71. package/dist/token/context-window.js +73 -0
  72. package/dist/token/index.d.ts +1 -0
  73. package/dist/token/index.js +1 -0
  74. package/dist/tool/create-tool-executor.d.ts +1 -1
  75. package/dist/tool/dev-http-executor.d.ts +2 -1
  76. package/dist/tool/dev-http-executor.js +14 -2
  77. package/dist/tool/dev-mcp-executor.d.ts +11 -1
  78. package/dist/tool/dev-mcp-executor.js +27 -1
  79. package/dist/tool/dev-tool-executor.d.ts +2 -1
  80. package/dist/tool/dev-tool-executor.js +6 -0
  81. package/dist/tool/harness-command.js +1 -1
  82. package/dist/tool/harness-primitives.d.ts +7 -0
  83. package/dist/tool/harness-primitives.js +57 -6
  84. package/dist/tool/http-tool-executor.js +1 -1
  85. package/dist/tool/index.d.ts +2 -1
  86. package/dist/tool/index.js +1 -0
  87. package/dist/tool/local-mcp-client.d.ts +5 -2
  88. package/dist/tool/local-mcp-client.js +130 -3
  89. package/dist/tool/local-tool-executor.d.ts +3 -2
  90. package/dist/tool/local-tool-executor.js +16 -1
  91. package/dist/tool/mcp-list.d.ts +53 -0
  92. package/dist/tool/mcp-list.js +199 -0
  93. package/dist/tool/mcp-tool-executor.d.ts +6 -1
  94. package/dist/tool/mcp-tool-executor.js +53 -1
  95. package/dist/tool/skill-dir-infer.d.ts +17 -0
  96. package/dist/tool/skill-dir-infer.js +81 -0
  97. package/dist/tool/skill-script-refs.d.ts +1 -8
  98. package/dist/tool/skill-script-refs.js +32 -20
  99. package/dist/tool/system-prompt-builder.d.ts +35 -16
  100. package/dist/tool/system-prompt-builder.js +30 -58
  101. package/dist/tool/timeout.d.ts +2 -2
  102. package/dist/tool/timeout.js +6 -3
  103. package/dist/tool/tool-execution-router.d.ts +3 -2
  104. package/dist/tool/tool-execution-router.js +8 -1
  105. package/dist/tool/tool-executor.interface.d.ts +11 -0
  106. package/dist/tool/tool-orchestrator.d.ts +1 -1
  107. package/dist/tool/tool-orchestrator.js +1 -1
  108. package/dist/utils/format-error.d.ts +2 -2
  109. package/dist/utils/format-error.js +2 -2
  110. package/dist/utils/http-error.d.ts +2 -2
  111. package/dist/utils/http-error.js +2 -2
  112. 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 里像脚本的相对路径已展开为绝对路径。不要把 cwd 设成技能目录。相对写出在用户目录。');
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 '@gt-fe/eap-contracts';
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 relativeBase = workdir ? (tryRealpath(workdir) ?? path.resolve(workdir)) : undefined;
348
- const target = resolveJailedPath(filePath, this.rootsFor(request), {
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
- return {
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 '@gt-fe/eap-contracts';
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;
@@ -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';
@@ -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 headers.userId 作兜底。
5
+ * 身份优先来自 Runtime ExecutionContext(Token.sub);manifest header.userId 作兜底。
6
6
  */
7
- import type { McpRuntimeExecution } from '@gt-fe/eap-contracts';
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 headers.userId 作兜底。
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 静态 headers.userId
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.headers ?? {};
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 '@gt-fe/eap-contracts';
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 '@gt-fe/eap-contracts';
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;