@gt-fe/eap-runtime 0.0.13 → 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 -66
- 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
- package/dist/registry/mcp-stdio-cwd.d.ts +0 -5
- package/dist/registry/mcp-stdio-cwd.js +0 -13
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本轮 prompt 占用的上下文窗:used = 本轮 inputTokens,window = ModelRecord.capabilities.maxContextTokens。
|
|
3
|
+
* 不是账单合计,也不是 max_tokens。
|
|
4
|
+
* parts 按本轮 system / messages / tools 字符比例把 used 拆开(无独立 tokenizer)。
|
|
5
|
+
*/
|
|
6
|
+
export interface ContextUsageParts {
|
|
7
|
+
system: number;
|
|
8
|
+
messages: number;
|
|
9
|
+
tools: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ContextWindowUsage {
|
|
12
|
+
used: number;
|
|
13
|
+
window?: number;
|
|
14
|
+
parts?: ContextUsageParts;
|
|
15
|
+
}
|
|
16
|
+
export declare function contextWindowUsage(used: number, window: number | undefined): ContextWindowUsage | undefined;
|
|
17
|
+
export declare function splitContextUsage(used: number, sections: {
|
|
18
|
+
systemChars: number;
|
|
19
|
+
messageChars: number;
|
|
20
|
+
toolChars: number;
|
|
21
|
+
}): ContextUsageParts | undefined;
|
|
22
|
+
export declare function turnSectionChars(config: {
|
|
23
|
+
systemPrompt?: string;
|
|
24
|
+
tools?: unknown[];
|
|
25
|
+
}, messages: Array<{
|
|
26
|
+
content?: string;
|
|
27
|
+
toolCalls?: unknown;
|
|
28
|
+
}>): {
|
|
29
|
+
systemChars: number;
|
|
30
|
+
messageChars: number;
|
|
31
|
+
toolChars: number;
|
|
32
|
+
};
|
|
33
|
+
export declare function buildContextUsage(used: number, window: number | undefined, sections?: {
|
|
34
|
+
systemChars: number;
|
|
35
|
+
messageChars: number;
|
|
36
|
+
toolChars: number;
|
|
37
|
+
}): ContextWindowUsage | undefined;
|
|
38
|
+
export declare function pickContextFields(raw: {
|
|
39
|
+
used?: unknown;
|
|
40
|
+
window?: unknown;
|
|
41
|
+
parts?: unknown;
|
|
42
|
+
}): Partial<ContextWindowUsage>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本轮 prompt 占用的上下文窗:used = 本轮 inputTokens,window = ModelRecord.capabilities.maxContextTokens。
|
|
3
|
+
* 不是账单合计,也不是 max_tokens。
|
|
4
|
+
* parts 按本轮 system / messages / tools 字符比例把 used 拆开(无独立 tokenizer)。
|
|
5
|
+
*/
|
|
6
|
+
export function contextWindowUsage(used, window) {
|
|
7
|
+
return buildContextUsage(used, window);
|
|
8
|
+
}
|
|
9
|
+
export function splitContextUsage(used, sections) {
|
|
10
|
+
if (!(used > 0))
|
|
11
|
+
return undefined;
|
|
12
|
+
const systemChars = Math.max(0, sections.systemChars);
|
|
13
|
+
const messageChars = Math.max(0, sections.messageChars);
|
|
14
|
+
const toolChars = Math.max(0, sections.toolChars);
|
|
15
|
+
const total = systemChars + messageChars + toolChars;
|
|
16
|
+
if (total <= 0)
|
|
17
|
+
return undefined;
|
|
18
|
+
const system = Math.floor((used * systemChars) / total);
|
|
19
|
+
const tools = Math.floor((used * toolChars) / total);
|
|
20
|
+
const messages = used - system - tools;
|
|
21
|
+
return { system, messages, tools };
|
|
22
|
+
}
|
|
23
|
+
export function turnSectionChars(config, messages) {
|
|
24
|
+
const systemChars = (config.systemPrompt ?? '').length;
|
|
25
|
+
let messageChars = 0;
|
|
26
|
+
for (const message of messages) {
|
|
27
|
+
if (message.content)
|
|
28
|
+
messageChars += message.content.length;
|
|
29
|
+
if (message.toolCalls)
|
|
30
|
+
messageChars += jsonChars(message.toolCalls);
|
|
31
|
+
}
|
|
32
|
+
const toolChars = config.tools?.length ? jsonChars(config.tools) : 0;
|
|
33
|
+
return { systemChars, messageChars, toolChars };
|
|
34
|
+
}
|
|
35
|
+
export function buildContextUsage(used, window, sections) {
|
|
36
|
+
const safeUsed = typeof used === 'number' && Number.isFinite(used) && used >= 0 ? Math.floor(used) : 0;
|
|
37
|
+
const win = typeof window === 'number' && Number.isFinite(window) && window > 0
|
|
38
|
+
? Math.floor(window)
|
|
39
|
+
: undefined;
|
|
40
|
+
const parts = sections ? splitContextUsage(safeUsed, sections) : undefined;
|
|
41
|
+
if (safeUsed === 0 && win == null && !parts)
|
|
42
|
+
return undefined;
|
|
43
|
+
return {
|
|
44
|
+
used: safeUsed,
|
|
45
|
+
...(win != null ? { window: win } : {}),
|
|
46
|
+
...(parts ? { parts } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function pickContextFields(raw) {
|
|
50
|
+
const out = {};
|
|
51
|
+
if (typeof raw.used === 'number' && Number.isFinite(raw.used))
|
|
52
|
+
out.used = raw.used;
|
|
53
|
+
if (typeof raw.window === 'number' && Number.isFinite(raw.window) && raw.window > 0) {
|
|
54
|
+
out.window = raw.window;
|
|
55
|
+
}
|
|
56
|
+
if (raw.parts && typeof raw.parts === 'object' && !Array.isArray(raw.parts)) {
|
|
57
|
+
const p = raw.parts;
|
|
58
|
+
if (typeof p.system === 'number'
|
|
59
|
+
&& typeof p.messages === 'number'
|
|
60
|
+
&& typeof p.tools === 'number') {
|
|
61
|
+
out.parts = { system: p.system, messages: p.messages, tools: p.tools };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
function jsonChars(value) {
|
|
67
|
+
try {
|
|
68
|
+
return JSON.stringify(value).length;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
package/dist/token/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { SessionBudgetChecker, SessionBudgetExhaustedError, } from './session-bu
|
|
|
7
7
|
export type { NodeTokenUsage, TokenBreakdown, TaskTokenSummary, } from './token-stats.service.js';
|
|
8
8
|
export { TokenStatsService } from './token-stats.service.js';
|
|
9
9
|
export type { TokenDetailMode } from './token-detail-mode.js';
|
|
10
|
+
export { contextWindowUsage, buildContextUsage, splitContextUsage, turnSectionChars, pickContextFields, type ContextWindowUsage, type ContextUsageParts, } from './context-window.js';
|
|
10
11
|
export { TokenStatsEmitter } from './token-detail-mode.js';
|
|
11
12
|
export { ContextWindowBudget, createContextBudget, type ContextWindowBudgetConfig, type ContextSource, type SourceAllocation, type BudgetAllocation, DEFAULT_CONTEXT_BUDGET_CONFIG, } from './context-window-budget.js';
|
|
12
13
|
export { CharBasedTokenEstimator, ModelBasedTokenEstimator, createTokenEstimator, type ITokenEstimator, } from './token-estimator.js';
|
package/dist/token/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { PostgresTokenBudgetRepository } from './pg-token-budget.repository.js';
|
|
|
3
3
|
export { TokenBudgetGuard, TokenBudgetExhaustedError, } from './token-budget-guard.js';
|
|
4
4
|
export { SessionBudgetChecker, SessionBudgetExhaustedError, } from './session-budget-checker.js';
|
|
5
5
|
export { TokenStatsService } from './token-stats.service.js';
|
|
6
|
+
export { contextWindowUsage, buildContextUsage, splitContextUsage, turnSectionChars, pickContextFields, } from './context-window.js';
|
|
6
7
|
export { TokenStatsEmitter } from './token-detail-mode.js';
|
|
7
8
|
// 上下文窗口预算分配
|
|
8
9
|
export { ContextWindowBudget, createContextBudget, DEFAULT_CONTEXT_BUDGET_CONFIG, } from './context-window-budget.js';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* `EAP_TOOL_GATEWAY_TRANSPORT=http` 时用 HttpToolExecutor(仅调试)。
|
|
6
6
|
* desktop 经 ToolExecutionRouter 分流 Local / Gateway。
|
|
7
7
|
*/
|
|
8
|
-
import type { ILocalToolInstallStore, LocalToolInstallRecord } from '@
|
|
8
|
+
import type { ILocalToolInstallStore, LocalToolInstallRecord } from '@eap/contracts';
|
|
9
9
|
import { type DeploymentContext } from './tool-execution-router.js';
|
|
10
10
|
import type { IToolExecutor } from './tool-executor.interface.js';
|
|
11
11
|
export declare class EmptyInstallStore implements ILocalToolInstallStore {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dev 下按 type:http 清单直连出站(gateway.baseUrl + pathTemplate)。
|
|
3
|
-
* URL 拼接与 host 白名单与 Tool Gateway 共用 @
|
|
3
|
+
* URL 拼接与 host 白名单与 Tool Gateway 共用 @eap/contracts http-outbound。
|
|
4
4
|
*/
|
|
5
5
|
import type { IToolExecutor, ToolCallRequest, ToolCallResult } from './tool-executor.interface.js';
|
|
6
6
|
export interface DevHttpExecution {
|
|
@@ -9,6 +9,7 @@ export interface DevHttpExecution {
|
|
|
9
9
|
baseUrl?: string;
|
|
10
10
|
pathTemplate?: string;
|
|
11
11
|
allowedHosts?: string[];
|
|
12
|
+
header?: Record<string, string>;
|
|
12
13
|
timeout?: number;
|
|
13
14
|
}
|
|
14
15
|
export declare function resolveHttpExecutionFromRequest(request: ToolCallRequest): DevHttpExecution | null;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dev 下按 type:http 清单直连出站(gateway.baseUrl + pathTemplate)。
|
|
3
|
-
* URL 拼接与 host 白名单与 Tool Gateway 共用 @
|
|
3
|
+
* URL 拼接与 host 白名单与 Tool Gateway 共用 @eap/contracts http-outbound。
|
|
4
4
|
*/
|
|
5
|
-
import { assertHttpHostAllowlisted, buildHttpToolUrl as buildSharedHttpToolUrl, ErrorCodes } from '@
|
|
5
|
+
import { assertHttpHostAllowlisted, buildHttpToolUrl as buildSharedHttpToolUrl, ErrorCodes } from '@eap/contracts';
|
|
6
6
|
import { abortOrTimeoutMessage, DEFAULT_HTTP_TOOL_TIMEOUT_SEC, isAbortError, resolveToolTimeoutSec, toToolTimeoutMs, } from './timeout.js';
|
|
7
7
|
export function resolveHttpExecutionFromRequest(request) {
|
|
8
8
|
const raw = request.execution;
|
|
@@ -23,9 +23,20 @@ export function resolveHttpExecutionFromRequest(request) {
|
|
|
23
23
|
allowedHosts: Array.isArray(e.allowedHosts)
|
|
24
24
|
? e.allowedHosts.filter((item) => typeof item === 'string')
|
|
25
25
|
: [],
|
|
26
|
+
header: readHeaderMap(e.header),
|
|
26
27
|
timeout: resolveToolTimeoutSec(e.timeout, DEFAULT_HTTP_TOOL_TIMEOUT_SEC),
|
|
27
28
|
};
|
|
28
29
|
}
|
|
30
|
+
function readHeaderMap(raw) {
|
|
31
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
32
|
+
return undefined;
|
|
33
|
+
const out = {};
|
|
34
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
35
|
+
if (typeof value === 'string')
|
|
36
|
+
out[key] = value;
|
|
37
|
+
}
|
|
38
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
39
|
+
}
|
|
29
40
|
export function buildHttpToolUrl(spec, args) {
|
|
30
41
|
const url = buildSharedHttpToolUrl(spec.baseUrl ?? '', spec.pathTemplate ?? '', args);
|
|
31
42
|
assertHttpHostAllowlisted(url, spec.allowedHosts ?? []);
|
|
@@ -54,6 +65,7 @@ export class DevHttpExecutor {
|
|
|
54
65
|
const timer = setTimeout(() => controller.abort(), toToolTimeoutMs(timeoutSec, DEFAULT_HTTP_TOOL_TIMEOUT_SEC));
|
|
55
66
|
const headers = {
|
|
56
67
|
Accept: 'application/json, text/plain;q=0.9',
|
|
68
|
+
...(spec.header ?? {}),
|
|
57
69
|
};
|
|
58
70
|
const init = { method, headers, signal: controller.signal };
|
|
59
71
|
if (method !== 'GET' && method !== 'HEAD') {
|
|
@@ -17,13 +17,23 @@
|
|
|
17
17
|
* @see docs/workflows/local-agent-dev-loop.md
|
|
18
18
|
* @see runtime/src/tool/local-mcp-client.ts
|
|
19
19
|
*/
|
|
20
|
+
import { type McpRuntimeExecution } from '@eap/contracts';
|
|
20
21
|
import { type ILocalMcpClient } from './local-mcp-client.js';
|
|
21
|
-
import type { IToolExecutor, ToolCallRequest, ToolCallResult } from './tool-executor.interface.js';
|
|
22
|
+
import type { IToolExecutor, McpListedTool, ToolCallRequest, ToolCallResult } from './tool-executor.interface.js';
|
|
22
23
|
export interface DevMcpExecutorOptions {
|
|
23
24
|
client?: ILocalMcpClient;
|
|
24
25
|
}
|
|
25
26
|
export declare class DevMcpExecutor implements IToolExecutor {
|
|
26
27
|
private readonly client;
|
|
28
|
+
private readonly listCache;
|
|
27
29
|
constructor(options?: DevMcpExecutorOptions);
|
|
28
30
|
execute(request: ToolCallRequest): Promise<ToolCallResult>;
|
|
31
|
+
listTools(request: ToolCallRequest): Promise<McpListedTool[]>;
|
|
29
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* 把 tool.manifest.execution(McpExecution 结构,transport 可为 stdio/sse/streamable_http)
|
|
35
|
+
* 规范化为 LocalMcpClient 消费的 McpRuntimeExecution(transport 只支持 stdio/streamable_http)。
|
|
36
|
+
*
|
|
37
|
+
* sse 归并到 streamable_http(LocalMcpClient 的 http 分支已能解析 SSE 响应)。
|
|
38
|
+
*/
|
|
39
|
+
export declare function normalizeMcpExecution(raw: unknown, fallbackToolName: string): McpRuntimeExecution | null;
|
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
* @see docs/workflows/local-agent-dev-loop.md
|
|
18
18
|
* @see runtime/src/tool/local-mcp-client.ts
|
|
19
19
|
*/
|
|
20
|
-
import { ErrorCodes } from '@
|
|
20
|
+
import { ErrorCodes } from '@eap/contracts';
|
|
21
21
|
import { isAbortError } from './timeout.js';
|
|
22
22
|
import { LocalMcpClient } from './local-mcp-client.js';
|
|
23
23
|
export class DevMcpExecutor {
|
|
24
24
|
client;
|
|
25
|
+
listCache = new Map();
|
|
25
26
|
constructor(options = {}) {
|
|
26
27
|
this.client = options.client ?? new LocalMcpClient();
|
|
27
28
|
}
|
|
@@ -64,6 +65,28 @@ export class DevMcpExecutor {
|
|
|
64
65
|
};
|
|
65
66
|
}
|
|
66
67
|
}
|
|
68
|
+
async listTools(request) {
|
|
69
|
+
const execution = normalizeExecution(request.execution, request.toolName);
|
|
70
|
+
if (!execution) {
|
|
71
|
+
throw new Error(`Dev MCP: tool "${request.toolName}" has no valid mcp execution config (expect manifest.execution.protocol="mcp")`);
|
|
72
|
+
}
|
|
73
|
+
const url = execution.server?.url ?? '';
|
|
74
|
+
const cmd = execution.server?.command ?? '';
|
|
75
|
+
const args = (execution.server?.args ?? []).join('\0');
|
|
76
|
+
const key = `${url || `stdio:${cmd}:${args}`}\0${request.authContext?.userId ?? ''}\0${request.authContext?.agentCode ?? ''}`;
|
|
77
|
+
const cached = this.listCache.get(key);
|
|
78
|
+
if (cached)
|
|
79
|
+
return cached;
|
|
80
|
+
const pending = this.client.list(execution, { userId: request.authContext?.userId });
|
|
81
|
+
this.listCache.set(key, pending);
|
|
82
|
+
try {
|
|
83
|
+
return await pending;
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
this.listCache.delete(key);
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
67
90
|
}
|
|
68
91
|
/**
|
|
69
92
|
* 把 tool.manifest.execution(McpExecution 结构,transport 可为 stdio/sse/streamable_http)
|
|
@@ -71,6 +94,9 @@ export class DevMcpExecutor {
|
|
|
71
94
|
*
|
|
72
95
|
* sse 归并到 streamable_http(LocalMcpClient 的 http 分支已能解析 SSE 响应)。
|
|
73
96
|
*/
|
|
97
|
+
export function normalizeMcpExecution(raw, fallbackToolName) {
|
|
98
|
+
return normalizeExecution(raw, fallbackToolName);
|
|
99
|
+
}
|
|
74
100
|
function normalizeExecution(raw, fallbackToolName) {
|
|
75
101
|
if (!raw || typeof raw !== 'object')
|
|
76
102
|
return null;
|
|
@@ -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))
|