@gt-fe/eap-runtime 0.0.8
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 +323 -0
- package/dist/api/handlers/index.d.ts +3 -0
- package/dist/api/handlers/index.js +4 -0
- package/dist/api/handlers/invoke.handler.d.ts +77 -0
- package/dist/api/handlers/invoke.handler.js +411 -0
- package/dist/api/handlers/messages.handler.d.ts +53 -0
- package/dist/api/handlers/messages.handler.js +80 -0
- package/dist/api/handlers/stream.handler.d.ts +92 -0
- package/dist/api/handlers/stream.handler.js +432 -0
- package/dist/api/index.d.ts +3 -0
- package/dist/api/index.js +4 -0
- package/dist/api/middleware/auth.middleware.d.ts +96 -0
- package/dist/api/middleware/auth.middleware.js +158 -0
- package/dist/api/middleware/index.d.ts +1 -0
- package/dist/api/middleware/index.js +2 -0
- package/dist/api/routes/agent.routes.d.ts +44 -0
- package/dist/api/routes/agent.routes.js +204 -0
- package/dist/api/routes/index.d.ts +3 -0
- package/dist/api/routes/index.js +4 -0
- package/dist/api/routes/session.routes.d.ts +82 -0
- package/dist/api/routes/session.routes.js +169 -0
- package/dist/api/routes/task.routes.d.ts +75 -0
- package/dist/api/routes/task.routes.js +108 -0
- package/dist/checkpoint/checkpoint-serializer.d.ts +58 -0
- package/dist/checkpoint/checkpoint-serializer.js +81 -0
- package/dist/checkpoint/composite-checkpoint-saver.d.ts +42 -0
- package/dist/checkpoint/composite-checkpoint-saver.js +118 -0
- package/dist/checkpoint/contracts/checkpoint-saver.contract.d.ts +65 -0
- package/dist/checkpoint/contracts/checkpoint-saver.contract.js +12 -0
- package/dist/checkpoint/contracts/index.d.ts +1 -0
- package/dist/checkpoint/contracts/index.js +1 -0
- package/dist/checkpoint/file-checkpoint-saver.d.ts +19 -0
- package/dist/checkpoint/file-checkpoint-saver.js +98 -0
- package/dist/checkpoint/hybrid-checkpoint-saver.d.ts +130 -0
- package/dist/checkpoint/hybrid-checkpoint-saver.js +211 -0
- package/dist/checkpoint/in-memory-checkpoint-saver.d.ts +14 -0
- package/dist/checkpoint/in-memory-checkpoint-saver.js +46 -0
- package/dist/checkpoint/index.d.ts +16 -0
- package/dist/checkpoint/index.js +13 -0
- package/dist/checkpoint/mongo-checkpoint-saver.d.ts +38 -0
- package/dist/checkpoint/mongo-checkpoint-saver.js +56 -0
- package/dist/checkpoint/pg-checkpoint-saver.d.ts +59 -0
- package/dist/checkpoint/pg-checkpoint-saver.js +140 -0
- package/dist/checkpoint/redis-checkpoint-saver.d.ts +68 -0
- package/dist/checkpoint/redis-checkpoint-saver.js +173 -0
- package/dist/checkpoint/restore-checkpoint.d.ts +33 -0
- package/dist/checkpoint/restore-checkpoint.js +33 -0
- package/dist/common/agent-code.d.ts +10 -0
- package/dist/common/agent-code.js +12 -0
- package/dist/common/concurrency.d.ts +79 -0
- package/dist/common/concurrency.js +121 -0
- package/dist/common/health.d.ts +40 -0
- package/dist/common/health.js +37 -0
- package/dist/common/index.d.ts +5 -0
- package/dist/common/index.js +5 -0
- package/dist/common/logger.d.ts +34 -0
- package/dist/common/logger.js +38 -0
- package/dist/common/queue-consumer.d.ts +125 -0
- package/dist/common/queue-consumer.js +159 -0
- package/dist/common/trace-id.d.ts +2 -0
- package/dist/common/trace-id.js +8 -0
- package/dist/common/types.d.ts +104 -0
- package/dist/common/types.js +14 -0
- package/dist/contracts/index.d.ts +11 -0
- package/dist/contracts/index.js +10 -0
- package/dist/contracts/permission-checker.d.ts +10 -0
- package/dist/contracts/permission-checker.js +4 -0
- package/dist/contracts/trace-collector.d.ts +211 -0
- package/dist/contracts/trace-collector.js +13 -0
- package/dist/governance/governance-client.d.ts +38 -0
- package/dist/governance/governance-client.js +95 -0
- package/dist/governance/index.d.ts +7 -0
- package/dist/governance/index.js +5 -0
- package/dist/governance/permission-guard.d.ts +19 -0
- package/dist/governance/permission-guard.js +59 -0
- package/dist/graph/adapters/index.d.ts +3 -0
- package/dist/graph/adapters/index.js +3 -0
- package/dist/graph/adapters/langgraph-checkpointer.d.ts +40 -0
- package/dist/graph/adapters/langgraph-checkpointer.js +211 -0
- package/dist/graph/adapters/langgraph.adapter.d.ts +190 -0
- package/dist/graph/adapters/langgraph.adapter.js +1303 -0
- package/dist/graph/adapters/subgraph-compiler.d.ts +39 -0
- package/dist/graph/adapters/subgraph-compiler.js +248 -0
- package/dist/graph/condition-evaluator.d.ts +57 -0
- package/dist/graph/condition-evaluator.js +162 -0
- package/dist/graph/contracts/executable-graph.d.ts +340 -0
- package/dist/graph/contracts/executable-graph.js +10 -0
- package/dist/graph/contracts/index.d.ts +2 -0
- package/dist/graph/contracts/index.js +2 -0
- package/dist/graph/contracts/state-graph.contract.d.ts +76 -0
- package/dist/graph/contracts/state-graph.contract.js +11 -0
- package/dist/graph/hitl/hitl-handler.d.ts +199 -0
- package/dist/graph/hitl/hitl-handler.js +480 -0
- package/dist/graph/hitl/index.d.ts +1 -0
- package/dist/graph/hitl/index.js +1 -0
- package/dist/graph/index.d.ts +7 -0
- package/dist/graph/index.js +7 -0
- package/dist/graph/nodes/index.d.ts +2 -0
- package/dist/graph/nodes/index.js +2 -0
- package/dist/graph/nodes/llm-node.d.ts +182 -0
- package/dist/graph/nodes/llm-node.js +281 -0
- package/dist/graph/nodes/skill-bind-node.d.ts +32 -0
- package/dist/graph/nodes/skill-bind-node.js +116 -0
- package/dist/graph/nodes/tool-node.d.ts +75 -0
- package/dist/graph/nodes/tool-node.js +324 -0
- package/dist/graph/resolve-graph-prompt.d.ts +5 -0
- package/dist/graph/resolve-graph-prompt.js +75 -0
- package/dist/graph/retry/budget-aware-retry.d.ts +91 -0
- package/dist/graph/retry/budget-aware-retry.js +126 -0
- package/dist/graph/retry/index.d.ts +9 -0
- package/dist/graph/retry/index.js +8 -0
- package/dist/graph/retry/node-retry.d.ts +123 -0
- package/dist/graph/retry/node-retry.js +235 -0
- package/dist/graph/skill-audit-recorder.d.ts +126 -0
- package/dist/graph/skill-audit-recorder.js +114 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +32 -0
- package/dist/memory/compression/compression-stats.d.ts +109 -0
- package/dist/memory/compression/compression-stats.js +144 -0
- package/dist/memory/compression/compression.repository.d.ts +62 -0
- package/dist/memory/compression/compression.repository.js +71 -0
- package/dist/memory/compression/index.d.ts +10 -0
- package/dist/memory/compression/index.js +15 -0
- package/dist/memory/compression/llm-summarize.d.ts +58 -0
- package/dist/memory/compression/llm-summarize.js +192 -0
- package/dist/memory/compression/sliding-window.compression.d.ts +60 -0
- package/dist/memory/compression/sliding-window.compression.js +175 -0
- package/dist/memory/index.d.ts +4 -0
- package/dist/memory/index.js +7 -0
- package/dist/memory/long-term-memory.service.d.ts +94 -0
- package/dist/memory/long-term-memory.service.js +120 -0
- package/dist/memory/message-compression.service.d.ts +128 -0
- package/dist/memory/message-compression.service.js +69 -0
- package/dist/memory/pg-memory.repository.d.ts +44 -0
- package/dist/memory/pg-memory.repository.js +108 -0
- package/dist/memory/semantic-retrieval.service.d.ts +148 -0
- package/dist/memory/semantic-retrieval.service.js +238 -0
- package/dist/providers/echo-llm-provider.d.ts +10 -0
- package/dist/providers/echo-llm-provider.js +87 -0
- package/dist/providers/http-llm-provider.d.ts +35 -0
- package/dist/providers/http-llm-provider.js +404 -0
- package/dist/providers/index.d.ts +7 -0
- package/dist/providers/index.js +5 -0
- package/dist/providers/local-llm-provider.d.ts +27 -0
- package/dist/providers/local-llm-provider.js +105 -0
- package/dist/providers/resolve-llm-model.d.ts +13 -0
- package/dist/providers/resolve-llm-model.js +30 -0
- package/dist/providers/routing-llm-provider.d.ts +28 -0
- package/dist/providers/routing-llm-provider.js +79 -0
- package/dist/registry/agent-models.d.ts +1 -0
- package/dist/registry/agent-models.js +1 -0
- package/dist/registry/agent-registry-client.d.ts +56 -0
- package/dist/registry/agent-registry-client.js +36 -0
- package/dist/registry/definition-cache.d.ts +37 -0
- package/dist/registry/definition-cache.js +82 -0
- package/dist/registry/deployment-snapshot.d.ts +99 -0
- package/dist/registry/deployment-snapshot.js +542 -0
- package/dist/registry/graph-loader.d.ts +134 -0
- package/dist/registry/graph-loader.js +985 -0
- package/dist/registry/index.d.ts +9 -0
- package/dist/registry/index.js +9 -0
- package/dist/registry/preload-errors.d.ts +13 -0
- package/dist/registry/preload-errors.js +35 -0
- package/dist/registry/preload-flags.d.ts +22 -0
- package/dist/registry/preload-flags.js +44 -0
- package/dist/registry/skill-io-guard.d.ts +25 -0
- package/dist/registry/skill-io-guard.js +98 -0
- package/dist/registry/skill-tree.d.ts +27 -0
- package/dist/registry/skill-tree.js +183 -0
- package/dist/runtime/agent-runtime.d.ts +106 -0
- package/dist/runtime/agent-runtime.js +777 -0
- package/dist/runtime/dev-runtime.d.ts +69 -0
- package/dist/runtime/dev-runtime.js +199 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/storage-profile.d.ts +50 -0
- package/dist/runtime/storage-profile.js +138 -0
- package/dist/runtime/types.d.ts +120 -0
- package/dist/runtime/types.js +9 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.js +161 -0
- package/dist/session/compression-middleware.d.ts +126 -0
- package/dist/session/compression-middleware.js +230 -0
- package/dist/session/file-message.repository.d.ts +25 -0
- package/dist/session/file-message.repository.js +78 -0
- package/dist/session/file-session.repository.d.ts +72 -0
- package/dist/session/file-session.repository.js +212 -0
- package/dist/session/index.d.ts +13 -0
- package/dist/session/index.js +12 -0
- package/dist/session/message.service.d.ts +80 -0
- package/dist/session/message.service.js +128 -0
- package/dist/session/persist-turn-messages.d.ts +145 -0
- package/dist/session/persist-turn-messages.js +325 -0
- package/dist/session/pg-message.repository.d.ts +49 -0
- package/dist/session/pg-message.repository.js +149 -0
- package/dist/session/pg-session.repository.d.ts +46 -0
- package/dist/session/pg-session.repository.js +117 -0
- package/dist/session/session-context.service.d.ts +49 -0
- package/dist/session/session-context.service.js +66 -0
- package/dist/session/session.service.d.ts +194 -0
- package/dist/session/session.service.js +166 -0
- package/dist/sse/connection-event-logger.d.ts +21 -0
- package/dist/sse/connection-event-logger.js +33 -0
- package/dist/sse/contracts/index.d.ts +1 -0
- package/dist/sse/contracts/index.js +1 -0
- package/dist/sse/contracts/sse-emitter.contract.d.ts +74 -0
- package/dist/sse/contracts/sse-emitter.contract.js +11 -0
- package/dist/sse/event-buffer.d.ts +20 -0
- package/dist/sse/event-buffer.js +45 -0
- package/dist/sse/events/code-events.d.ts +19 -0
- package/dist/sse/events/code-events.js +9 -0
- package/dist/sse/events/collaboration-events.d.ts +37 -0
- package/dist/sse/events/collaboration-events.js +11 -0
- package/dist/sse/events/connection-events.d.ts +13 -0
- package/dist/sse/events/connection-events.js +9 -0
- package/dist/sse/events/file-events.d.ts +29 -0
- package/dist/sse/events/file-events.js +10 -0
- package/dist/sse/events/hitl-events.d.ts +33 -0
- package/dist/sse/events/hitl-events.js +10 -0
- package/dist/sse/events/index.d.ts +107 -0
- package/dist/sse/events/index.js +96 -0
- package/dist/sse/events/plan-events.d.ts +38 -0
- package/dist/sse/events/plan-events.js +11 -0
- package/dist/sse/events/reasoning-events.d.ts +35 -0
- package/dist/sse/events/reasoning-events.js +13 -0
- package/dist/sse/events/response-events.d.ts +25 -0
- package/dist/sse/events/response-events.js +12 -0
- package/dist/sse/events/session-events.d.ts +43 -0
- package/dist/sse/events/session-events.js +12 -0
- package/dist/sse/events/skill-events.d.ts +48 -0
- package/dist/sse/events/skill-events.js +11 -0
- package/dist/sse/events/stats-events.d.ts +44 -0
- package/dist/sse/events/stats-events.js +12 -0
- package/dist/sse/events/tool-events.d.ts +68 -0
- package/dist/sse/events/tool-events.js +14 -0
- package/dist/sse/index.d.ts +8 -0
- package/dist/sse/index.js +8 -0
- package/dist/sse/sse-connection-manager.d.ts +116 -0
- package/dist/sse/sse-connection-manager.js +185 -0
- package/dist/sse/sse-emitter.d.ts +108 -0
- package/dist/sse/sse-emitter.js +210 -0
- package/dist/sse/sse-heartbeat.d.ts +58 -0
- package/dist/sse/sse-heartbeat.js +76 -0
- package/dist/sse/trace-collector.d.ts +60 -0
- package/dist/sse/trace-collector.js +107 -0
- package/dist/task/contracts/index.d.ts +2 -0
- package/dist/task/contracts/index.js +1 -0
- package/dist/task/contracts/task-manager.contract.d.ts +160 -0
- package/dist/task/contracts/task-manager.contract.js +10 -0
- package/dist/task/index.d.ts +7 -0
- package/dist/task/index.js +5 -0
- package/dist/task/pg-task.repository.d.ts +47 -0
- package/dist/task/pg-task.repository.js +131 -0
- package/dist/task/task-progress-tracker.d.ts +67 -0
- package/dist/task/task-progress-tracker.js +80 -0
- package/dist/task/task-timeout.d.ts +70 -0
- package/dist/task/task-timeout.js +116 -0
- package/dist/task/task.service.d.ts +84 -0
- package/dist/task/task.service.js +233 -0
- package/dist/token/compression-budget-integration.d.ts +145 -0
- package/dist/token/compression-budget-integration.js +185 -0
- package/dist/token/context-window-budget.d.ts +113 -0
- package/dist/token/context-window-budget.js +134 -0
- package/dist/token/contracts/index.d.ts +1 -0
- package/dist/token/contracts/index.js +1 -0
- package/dist/token/contracts/token-budget.contract.d.ts +77 -0
- package/dist/token/contracts/token-budget.contract.js +11 -0
- package/dist/token/index.d.ts +12 -0
- package/dist/token/index.js +10 -0
- package/dist/token/pg-token-budget.repository.d.ts +39 -0
- package/dist/token/pg-token-budget.repository.js +116 -0
- package/dist/token/session-budget-checker.d.ts +54 -0
- package/dist/token/session-budget-checker.js +72 -0
- package/dist/token/token-budget-guard.d.ts +66 -0
- package/dist/token/token-budget-guard.js +97 -0
- package/dist/token/token-budget.service.d.ts +86 -0
- package/dist/token/token-budget.service.js +130 -0
- package/dist/token/token-detail-mode.d.ts +73 -0
- package/dist/token/token-detail-mode.js +107 -0
- package/dist/token/token-estimator.d.ts +80 -0
- package/dist/token/token-estimator.js +134 -0
- package/dist/token/token-stats.service.d.ts +88 -0
- package/dist/token/token-stats.service.js +94 -0
- package/dist/tool/create-tool-executor.d.ts +33 -0
- package/dist/tool/create-tool-executor.js +60 -0
- package/dist/tool/dev-http-executor.d.ts +18 -0
- package/dist/tool/dev-http-executor.js +96 -0
- package/dist/tool/dev-mcp-executor.d.ts +29 -0
- package/dist/tool/dev-mcp-executor.js +84 -0
- package/dist/tool/dev-tool-executor.d.ts +23 -0
- package/dist/tool/dev-tool-executor.js +55 -0
- package/dist/tool/file-artifact.d.ts +11 -0
- package/dist/tool/file-artifact.js +90 -0
- package/dist/tool/http-tool-executor.d.ts +17 -0
- package/dist/tool/http-tool-executor.js +158 -0
- package/dist/tool/index.d.ts +18 -0
- package/dist/tool/index.js +17 -0
- package/dist/tool/loaded-tool-definition.d.ts +35 -0
- package/dist/tool/loaded-tool-definition.js +9 -0
- package/dist/tool/local-mcp-client.d.ts +23 -0
- package/dist/tool/local-mcp-client.js +225 -0
- package/dist/tool/local-tool-executor.d.ts +37 -0
- package/dist/tool/local-tool-executor.js +249 -0
- package/dist/tool/mcp-tool-executor.d.ts +23 -0
- package/dist/tool/mcp-tool-executor.js +160 -0
- package/dist/tool/mock-tool-executor.d.ts +28 -0
- package/dist/tool/mock-tool-executor.js +65 -0
- package/dist/tool/system-prompt-builder.d.ts +114 -0
- package/dist/tool/system-prompt-builder.js +213 -0
- package/dist/tool/tool-confirmation.d.ts +29 -0
- package/dist/tool/tool-confirmation.js +81 -0
- package/dist/tool/tool-event-emitter.d.ts +58 -0
- package/dist/tool/tool-event-emitter.js +154 -0
- package/dist/tool/tool-execution-router.d.ts +25 -0
- package/dist/tool/tool-execution-router.js +50 -0
- package/dist/tool/tool-executor.interface.d.ts +85 -0
- package/dist/tool/tool-executor.interface.js +7 -0
- package/dist/tool/tool-orchestrator.d.ts +22 -0
- package/dist/tool/tool-orchestrator.js +104 -0
- package/dist/tool/tool-result-injector.d.ts +64 -0
- package/dist/tool/tool-result-injector.js +65 -0
- package/dist/utils/format-error.d.ts +4 -0
- package/dist/utils/format-error.js +4 -0
- package/dist/utils/http-error.d.ts +11 -0
- package/dist/utils/http-error.js +24 -0
- package/package.json +56 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevToolExecutor — 本地 dev 总入口(不是 code 专用)
|
|
3
|
+
*
|
|
4
|
+
* 仅在 `EAP_TOOL_EXECUTION_MODE=mcp` 且 `deploymentContext=dev` 装配。
|
|
5
|
+
* 按清单分流,缺配置则失败,不回落 mock。
|
|
6
|
+
*
|
|
7
|
+
* - execution.protocol=mcp → DevMcpExecutor
|
|
8
|
+
* - execution.protocol=http → DevHttpExecutor
|
|
9
|
+
* - 能解析出 code handler → LocalToolExecutor
|
|
10
|
+
* - 其余 → NOT_CONFIGURED(mock 只走显式 `--tool-mode mock`)
|
|
11
|
+
*/
|
|
12
|
+
import { EmptyInstallStore } from './create-tool-executor.js';
|
|
13
|
+
import { DevMcpExecutor } from './dev-mcp-executor.js';
|
|
14
|
+
import { LocalToolExecutor, resolveCodeExecutionFromRequest, } from './local-tool-executor.js';
|
|
15
|
+
import { DevHttpExecutor, resolveHttpExecutionFromRequest } from './dev-http-executor.js';
|
|
16
|
+
export class DevToolExecutor {
|
|
17
|
+
mcp;
|
|
18
|
+
http;
|
|
19
|
+
local;
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.mcp = new DevMcpExecutor(options.mcp);
|
|
22
|
+
this.http = new DevHttpExecutor();
|
|
23
|
+
this.local = new LocalToolExecutor({ installStore: new EmptyInstallStore() });
|
|
24
|
+
}
|
|
25
|
+
async execute(request) {
|
|
26
|
+
if (hasMcpExecution(request.execution)) {
|
|
27
|
+
return this.mcp.execute(request);
|
|
28
|
+
}
|
|
29
|
+
if (resolveHttpExecutionFromRequest(request)) {
|
|
30
|
+
return this.http.execute(request);
|
|
31
|
+
}
|
|
32
|
+
const codeSpec = resolveCodeExecutionFromRequest(request);
|
|
33
|
+
if (codeSpec) {
|
|
34
|
+
return this.local.execute({
|
|
35
|
+
...request,
|
|
36
|
+
execution: { ...request.execution, localPreferred: true },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
toolCallId: request.toolCallId,
|
|
41
|
+
toolName: request.toolName,
|
|
42
|
+
content: '',
|
|
43
|
+
success: false,
|
|
44
|
+
error: `Dev tool "${request.toolName}" is not executable: no MCP server, ` +
|
|
45
|
+
'HTTP gateway+pathTemplate, or code handler. Use --tool-mode mock for MockToolExecutor.',
|
|
46
|
+
errorCode: 'NOT_CONFIGURED',
|
|
47
|
+
runtime: 'local',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function hasMcpExecution(raw) {
|
|
52
|
+
if (!raw || typeof raw !== 'object')
|
|
53
|
+
return false;
|
|
54
|
+
return raw.protocol === 'mcp';
|
|
55
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface FileArtifact {
|
|
2
|
+
fileName: string;
|
|
3
|
+
fileType: string;
|
|
4
|
+
size: number;
|
|
5
|
+
url: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function parseJsonValue(value: unknown): unknown;
|
|
8
|
+
export declare function extractFileReference(value: unknown): Record<string, unknown> | undefined;
|
|
9
|
+
export declare function fileBasesFromEnv(serverEnv?: Record<string, string | undefined>): string[];
|
|
10
|
+
export declare function resolveFileArtifact(ref: Record<string, unknown>, fileBases?: Array<string | undefined>): FileArtifact | undefined;
|
|
11
|
+
export declare function fileArtifactFromToolOutput(content: unknown, fileBases?: Array<string | undefined>): FileArtifact | undefined;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 从 Tool 输出里抽出通用文件产物(fileReference / 顶层 fileName+url)。
|
|
3
|
+
* 不认具体工具名。有公网 url 用 url;否则用 MCP_FILE_BASE_PATH 等根目录拼成本地 file://。
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { pathToFileURL } from 'node:url';
|
|
8
|
+
export function parseJsonValue(value) {
|
|
9
|
+
if (typeof value !== 'string')
|
|
10
|
+
return value;
|
|
11
|
+
const trimmed = value.trim();
|
|
12
|
+
if (!trimmed.startsWith('{') && !trimmed.startsWith('['))
|
|
13
|
+
return value;
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(trimmed);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function extractFileReference(value) {
|
|
22
|
+
const parsed = parseJsonValue(value);
|
|
23
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
24
|
+
return undefined;
|
|
25
|
+
const obj = parsed;
|
|
26
|
+
const nested = obj.fileReference;
|
|
27
|
+
if (nested && typeof nested === 'object' && !Array.isArray(nested)) {
|
|
28
|
+
return nested;
|
|
29
|
+
}
|
|
30
|
+
if (typeof obj.fileName === 'string'
|
|
31
|
+
&& (typeof obj.url === 'string' || typeof obj.relativePath === 'string')) {
|
|
32
|
+
return obj;
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
export function fileBasesFromEnv(serverEnv) {
|
|
37
|
+
return [
|
|
38
|
+
serverEnv?.MCP_FILE_BASE_PATH,
|
|
39
|
+
process.env.MCP_FILE_BASE_PATH,
|
|
40
|
+
process.env.EAP_FILE_BASE_PATH,
|
|
41
|
+
].filter((value) => Boolean(value?.trim()));
|
|
42
|
+
}
|
|
43
|
+
export function resolveFileArtifact(ref, fileBases = []) {
|
|
44
|
+
const relativePath = asString(ref.relativePath);
|
|
45
|
+
const fileName = asString(ref.fileName)
|
|
46
|
+
?? (relativePath ? path.basename(relativePath) : undefined);
|
|
47
|
+
if (!fileName)
|
|
48
|
+
return undefined;
|
|
49
|
+
const fileType = asString(ref.format) ?? asString(ref.fileType) ?? extOf(fileName);
|
|
50
|
+
const size = typeof ref.size === 'number' && Number.isFinite(ref.size) ? ref.size : 0;
|
|
51
|
+
const explicitUrl = asString(ref.url);
|
|
52
|
+
if (explicitUrl) {
|
|
53
|
+
return { fileName, fileType, size, url: explicitUrl };
|
|
54
|
+
}
|
|
55
|
+
if (!relativePath || hasParentSegment(relativePath))
|
|
56
|
+
return undefined;
|
|
57
|
+
const bases = fileBases.filter((base) => Boolean(base?.trim()));
|
|
58
|
+
if (path.isAbsolute(relativePath)) {
|
|
59
|
+
const abs = path.resolve(relativePath);
|
|
60
|
+
if (bases.some((base) => isInside(base, abs)) && existsSync(abs)) {
|
|
61
|
+
return { fileName, fileType, size, url: pathToFileURL(abs).href };
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
for (const base of bases) {
|
|
66
|
+
const abs = path.resolve(base, relativePath);
|
|
67
|
+
if (!isInside(base, abs) || !existsSync(abs))
|
|
68
|
+
continue;
|
|
69
|
+
return { fileName, fileType, size, url: pathToFileURL(abs).href };
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
export function fileArtifactFromToolOutput(content, fileBases = []) {
|
|
74
|
+
const ref = extractFileReference(content);
|
|
75
|
+
return ref ? resolveFileArtifact(ref, fileBases) : undefined;
|
|
76
|
+
}
|
|
77
|
+
function asString(value) {
|
|
78
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
79
|
+
}
|
|
80
|
+
function extOf(fileName) {
|
|
81
|
+
const ext = path.extname(fileName).replace(/^\./, '');
|
|
82
|
+
return ext || 'file';
|
|
83
|
+
}
|
|
84
|
+
function hasParentSegment(rel) {
|
|
85
|
+
return rel.split(/[\\/]/).some((part) => part === '..');
|
|
86
|
+
}
|
|
87
|
+
function isInside(root, target) {
|
|
88
|
+
const relativePath = path.relative(path.resolve(root), path.resolve(target));
|
|
89
|
+
return relativePath === '' || (!relativePath.startsWith(`..${path.sep}`) && relativePath !== '..');
|
|
90
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HttpToolExecutor — 基于 HTTP 的 Tool 执行器实现
|
|
3
|
+
*
|
|
4
|
+
* @see docs/contracts/tool-execution.md §2
|
|
5
|
+
* @see docs/contracts/cross-service-api.md §4.6
|
|
6
|
+
*/
|
|
7
|
+
import type { IToolExecutor, ToolCallRequest, ToolCallResult } from './tool-executor.interface.js';
|
|
8
|
+
export interface HttpToolExecutorOptions {
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
timeout?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class HttpToolExecutor implements IToolExecutor {
|
|
13
|
+
private readonly baseUrl;
|
|
14
|
+
private readonly timeout;
|
|
15
|
+
constructor(options: HttpToolExecutorOptions);
|
|
16
|
+
execute(request: ToolCallRequest): Promise<ToolCallResult>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HttpToolExecutor — 基于 HTTP 的 Tool 执行器实现
|
|
3
|
+
*
|
|
4
|
+
* @see docs/contracts/tool-execution.md §2
|
|
5
|
+
* @see docs/contracts/cross-service-api.md §4.6
|
|
6
|
+
*/
|
|
7
|
+
function mapGatewayHttpError(status, body, statusText) {
|
|
8
|
+
const code = body?.error?.code;
|
|
9
|
+
const message = body?.error?.message ?? body?.message ?? statusText;
|
|
10
|
+
if (status === 403) {
|
|
11
|
+
return {
|
|
12
|
+
error: message,
|
|
13
|
+
errorCode: code ?? 'NO_PERMISSION',
|
|
14
|
+
retryable: false,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (status === 429) {
|
|
18
|
+
return {
|
|
19
|
+
error: message,
|
|
20
|
+
errorCode: code ?? 'RATE_LIMITED',
|
|
21
|
+
retryable: body?.error?.retryable ?? true,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (status === 503) {
|
|
25
|
+
return {
|
|
26
|
+
error: message,
|
|
27
|
+
errorCode: code ?? 'CIRCUIT_OPEN',
|
|
28
|
+
retryable: body?.error?.retryable ?? true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (status === 404) {
|
|
32
|
+
return {
|
|
33
|
+
error: message,
|
|
34
|
+
errorCode: code ?? 'TOOL_NOT_FOUND',
|
|
35
|
+
retryable: false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (status === 400) {
|
|
39
|
+
return {
|
|
40
|
+
error: message,
|
|
41
|
+
errorCode: code ?? 'INVALID_ARGUMENTS',
|
|
42
|
+
retryable: false,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
error: message || `HTTP ${status}`,
|
|
47
|
+
errorCode: code ?? 'EXECUTION_FAILED',
|
|
48
|
+
retryable: false,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export class HttpToolExecutor {
|
|
52
|
+
baseUrl;
|
|
53
|
+
timeout;
|
|
54
|
+
constructor(options) {
|
|
55
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, '');
|
|
56
|
+
this.timeout = options.timeout ?? 30_000;
|
|
57
|
+
}
|
|
58
|
+
async execute(request) {
|
|
59
|
+
const url = `${this.baseUrl}/api/v1/tools/${encodeURIComponent(request.toolName)}/execute`;
|
|
60
|
+
const agentCode = request.authContext.agentCode ?? request.authContext.agentId;
|
|
61
|
+
const correlationId = request.correlationId ?? request.toolCallId;
|
|
62
|
+
const taskId = request.taskId ?? request.toolCallId;
|
|
63
|
+
const body = {
|
|
64
|
+
arguments: request.arguments,
|
|
65
|
+
caller: {
|
|
66
|
+
userId: request.authContext.userId,
|
|
67
|
+
agentId: agentCode,
|
|
68
|
+
agentCode,
|
|
69
|
+
delegationToken: request.authContext.delegationToken,
|
|
70
|
+
taskId,
|
|
71
|
+
correlationId,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const headers = {
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
Authorization: `Bearer ${request.authContext.delegationToken}`,
|
|
77
|
+
'X-Agent-Id': agentCode,
|
|
78
|
+
'X-Agent-Code': agentCode,
|
|
79
|
+
'X-User-Id': request.authContext.userId,
|
|
80
|
+
userId: request.authContext.userId,
|
|
81
|
+
'X-Correlation-Id': correlationId,
|
|
82
|
+
'X-Task-Id': taskId,
|
|
83
|
+
};
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
86
|
+
try {
|
|
87
|
+
const response = await fetch(url, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers,
|
|
90
|
+
body: JSON.stringify(body),
|
|
91
|
+
signal: controller.signal,
|
|
92
|
+
});
|
|
93
|
+
clearTimeout(timeoutId);
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
let errorBody = null;
|
|
96
|
+
try {
|
|
97
|
+
errorBody = (await response.json());
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
errorBody = null;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
toolCallId: request.toolCallId,
|
|
104
|
+
toolName: request.toolName,
|
|
105
|
+
content: '',
|
|
106
|
+
success: false,
|
|
107
|
+
runtime: 'server',
|
|
108
|
+
...mapGatewayHttpError(response.status, errorBody, response.statusText),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const raw = (await response.json());
|
|
112
|
+
const data = raw.data ?? raw;
|
|
113
|
+
const output = data.output
|
|
114
|
+
?? data.content
|
|
115
|
+
?? '';
|
|
116
|
+
const content = typeof output === 'string' ? output : JSON.stringify(output);
|
|
117
|
+
const success = raw.code === 0 ||
|
|
118
|
+
raw.code === 'ok' ||
|
|
119
|
+
raw.success === true ||
|
|
120
|
+
(raw.success === undefined && raw.code !== 'error');
|
|
121
|
+
const gatewayError = typeof raw.error === 'object' && raw.error !== null ? raw.error : undefined;
|
|
122
|
+
return {
|
|
123
|
+
toolCallId: request.toolCallId,
|
|
124
|
+
toolName: request.toolName,
|
|
125
|
+
content,
|
|
126
|
+
success,
|
|
127
|
+
error: gatewayError?.message ?? (typeof raw.error === 'string' ? raw.error : undefined),
|
|
128
|
+
errorCode: gatewayError?.code,
|
|
129
|
+
retryable: gatewayError?.retryable,
|
|
130
|
+
runtime: 'server',
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
clearTimeout(timeoutId);
|
|
135
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
136
|
+
return {
|
|
137
|
+
toolCallId: request.toolCallId,
|
|
138
|
+
toolName: request.toolName,
|
|
139
|
+
content: '',
|
|
140
|
+
success: false,
|
|
141
|
+
error: `Request timeout after ${this.timeout}ms`,
|
|
142
|
+
errorCode: 'EXECUTION_FAILED',
|
|
143
|
+
runtime: 'server',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const message = error instanceof Error ? error.message : 'Unknown network error';
|
|
147
|
+
return {
|
|
148
|
+
toolCallId: request.toolCallId,
|
|
149
|
+
toolName: request.toolName,
|
|
150
|
+
content: '',
|
|
151
|
+
success: false,
|
|
152
|
+
error: message,
|
|
153
|
+
errorCode: 'EXECUTION_FAILED',
|
|
154
|
+
runtime: 'server',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type { AuthContext, ToolCallRequest, ToolCallResult, LLMToolCall, LLMToolCallResponse, IToolExecutor, } from './tool-executor.interface.js';
|
|
2
|
+
export { toLoadedToolMap, type LoadedToolDefinition, type LoadedToolMap, type LoadedToolManifestRef, } from './loaded-tool-definition.js';
|
|
3
|
+
export { ToolOrchestrator } from './tool-orchestrator.js';
|
|
4
|
+
export { HttpToolExecutor, type HttpToolExecutorOptions, } from './http-tool-executor.js';
|
|
5
|
+
export { McpToolExecutor, type McpToolExecutorOptions, } from './mcp-tool-executor.js';
|
|
6
|
+
export { LocalToolExecutor, isLocalPreferredExecution, resolveCodeExecutionFromRequest } from './local-tool-executor.js';
|
|
7
|
+
export { DevMcpExecutor, type DevMcpExecutorOptions, } from './dev-mcp-executor.js';
|
|
8
|
+
export { DevHttpExecutor, buildHttpToolUrl, resolveHttpExecutionFromRequest, type DevHttpExecution, } from './dev-http-executor.js';
|
|
9
|
+
export { DevToolExecutor, type DevToolExecutorOptions } from './dev-tool-executor.js';
|
|
10
|
+
export { MockToolExecutor, type MockToolExecutorOptions, } from './mock-tool-executor.js';
|
|
11
|
+
export { LocalMcpClient, type ILocalMcpClient, type LocalMcpInvokeContext, type LocalMcpInvokeResult, } from './local-mcp-client.js';
|
|
12
|
+
export { fileArtifactFromToolOutput, extractFileReference, resolveFileArtifact, type FileArtifact, } from './file-artifact.js';
|
|
13
|
+
export { ToolExecutionRouter, type DeploymentContext, type ToolExecutionRouterOptions, } from './tool-execution-router.js';
|
|
14
|
+
export { createToolExecutor, EmptyInstallStore, type CreateToolExecutorOptions, } from './create-tool-executor.js';
|
|
15
|
+
export { ToolResultInjector, type ToolMessage, } from './tool-result-injector.js';
|
|
16
|
+
export { ToolEventEmitter } from './tool-event-emitter.js';
|
|
17
|
+
export { ToolConfirmationInterceptor } from './tool-confirmation.js';
|
|
18
|
+
export { SystemPromptBuilder, loadedToolsToApiTools, toLoadedSkillMap, mergeSkillScriptTools, filterLoadedToolsForSkill, type PromptBuildContext, type ApiToolDefinition, type LoadedSkillInfo, type LoadedSkillMap, } from './system-prompt-builder.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { toLoadedToolMap, } from './loaded-tool-definition.js';
|
|
2
|
+
export { ToolOrchestrator } from './tool-orchestrator.js';
|
|
3
|
+
export { HttpToolExecutor, } from './http-tool-executor.js';
|
|
4
|
+
export { McpToolExecutor, } from './mcp-tool-executor.js';
|
|
5
|
+
export { LocalToolExecutor, isLocalPreferredExecution, resolveCodeExecutionFromRequest } from './local-tool-executor.js';
|
|
6
|
+
export { DevMcpExecutor, } from './dev-mcp-executor.js';
|
|
7
|
+
export { DevHttpExecutor, buildHttpToolUrl, resolveHttpExecutionFromRequest, } from './dev-http-executor.js';
|
|
8
|
+
export { DevToolExecutor } from './dev-tool-executor.js';
|
|
9
|
+
export { MockToolExecutor, } from './mock-tool-executor.js';
|
|
10
|
+
export { LocalMcpClient, } from './local-mcp-client.js';
|
|
11
|
+
export { fileArtifactFromToolOutput, extractFileReference, resolveFileArtifact, } from './file-artifact.js';
|
|
12
|
+
export { ToolExecutionRouter, } from './tool-execution-router.js';
|
|
13
|
+
export { createToolExecutor, EmptyInstallStore, } from './create-tool-executor.js';
|
|
14
|
+
export { ToolResultInjector, } from './tool-result-injector.js';
|
|
15
|
+
export { ToolEventEmitter } from './tool-event-emitter.js';
|
|
16
|
+
export { ToolConfirmationInterceptor } from './tool-confirmation.js';
|
|
17
|
+
export { SystemPromptBuilder, loadedToolsToApiTools, toLoadedSkillMap, mergeSkillScriptTools, filterLoadedToolsForSkill, } from './system-prompt-builder.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphLoader 产出的运行期 Tool 定义
|
|
3
|
+
*
|
|
4
|
+
* @see docs/contracts/tool-execution.md §3.2
|
|
5
|
+
* @see .kiro/specs/04-runtime-core/tasks.md Task 18
|
|
6
|
+
*/
|
|
7
|
+
export type LoadedToolType = 'code' | 'mcp' | 'http';
|
|
8
|
+
export type LoadedToolRuntime = 'local' | 'server' | 'both';
|
|
9
|
+
export type LoadedDataScope = 'user-bound' | 'department-bound' | 'global';
|
|
10
|
+
export interface LoadedToolManifestRef {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
version: string;
|
|
14
|
+
type: LoadedToolType;
|
|
15
|
+
description?: string;
|
|
16
|
+
status?: string;
|
|
17
|
+
toolRuntime?: LoadedToolRuntime;
|
|
18
|
+
dataScope?: LoadedDataScope;
|
|
19
|
+
requireConfirmation?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* MCP/HTTP 出站执行配置(来自 tool.manifest.execution)
|
|
22
|
+
*
|
|
23
|
+
* dev 上下文 DevMcpExecutor 消费此字段做直连(stdio / streamable_http / sse);
|
|
24
|
+
* server 上下文 gateway 会忽略此字段,改由 foundation-service 侧根据 toolName 分派。
|
|
25
|
+
*/
|
|
26
|
+
execution?: unknown;
|
|
27
|
+
}
|
|
28
|
+
export interface LoadedToolDefinition {
|
|
29
|
+
toolId: string;
|
|
30
|
+
name: string;
|
|
31
|
+
manifest: LoadedToolManifestRef;
|
|
32
|
+
inputSchema?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
export type LoadedToolMap = Record<string, LoadedToolDefinition>;
|
|
35
|
+
export declare function toLoadedToolMap(definitions: LoadedToolDefinition[]): LoadedToolMap;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalMcpClient — 桌面本地 MCP 调用(stdio / streamable_http)
|
|
3
|
+
*
|
|
4
|
+
* mcp-data-service 等上游要求 HTTP 头 `userId`(或 query),再注入 `params._meta.userId`。
|
|
5
|
+
* 身份优先来自 Runtime ExecutionContext(Token.sub);manifest headers.userId 作兜底。
|
|
6
|
+
*/
|
|
7
|
+
import type { McpRuntimeExecution } from '@gt-fe/eap-contracts';
|
|
8
|
+
import { type FileArtifact } from './file-artifact.js';
|
|
9
|
+
export interface LocalMcpInvokeContext {
|
|
10
|
+
/** Platform Token.sub / ExecutionContext.userId */
|
|
11
|
+
userId?: string | null;
|
|
12
|
+
}
|
|
13
|
+
export interface LocalMcpInvokeResult {
|
|
14
|
+
content: string;
|
|
15
|
+
file?: FileArtifact;
|
|
16
|
+
}
|
|
17
|
+
export interface ILocalMcpClient {
|
|
18
|
+
invoke(execution: McpRuntimeExecution, args: Record<string, unknown>, context?: LocalMcpInvokeContext): Promise<LocalMcpInvokeResult>;
|
|
19
|
+
}
|
|
20
|
+
export declare function formatMcpOutput(result: unknown): string;
|
|
21
|
+
export declare class LocalMcpClient implements ILocalMcpClient {
|
|
22
|
+
invoke(execution: McpRuntimeExecution, args: Record<string, unknown>, context?: LocalMcpInvokeContext): Promise<LocalMcpInvokeResult>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalMcpClient — 桌面本地 MCP 调用(stdio / streamable_http)
|
|
3
|
+
*
|
|
4
|
+
* mcp-data-service 等上游要求 HTTP 头 `userId`(或 query),再注入 `params._meta.userId`。
|
|
5
|
+
* 身份优先来自 Runtime ExecutionContext(Token.sub);manifest headers.userId 作兜底。
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import readline from 'node:readline';
|
|
9
|
+
import { fileArtifactFromToolOutput, fileBasesFromEnv } from './file-artifact.js';
|
|
10
|
+
export function formatMcpOutput(result) {
|
|
11
|
+
const r = result;
|
|
12
|
+
if (r?.structuredContent !== undefined) {
|
|
13
|
+
return JSON.stringify(redactFileLocators(r.structuredContent));
|
|
14
|
+
}
|
|
15
|
+
const text = r?.content?.[0]?.text;
|
|
16
|
+
if (text) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.stringify(redactFileLocators(JSON.parse(text)));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return text;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return JSON.stringify(redactFileLocators(result));
|
|
25
|
+
}
|
|
26
|
+
function redactFileLocators(value) {
|
|
27
|
+
if (Array.isArray(value))
|
|
28
|
+
return value.map(redactFileLocators);
|
|
29
|
+
if (!value || typeof value !== 'object')
|
|
30
|
+
return value;
|
|
31
|
+
const obj = value;
|
|
32
|
+
const fileShaped = typeof obj.fileName === 'string'
|
|
33
|
+
&& (typeof obj.url === 'string' || typeof obj.relativePath === 'string');
|
|
34
|
+
const out = {};
|
|
35
|
+
for (const [key, child] of Object.entries(obj)) {
|
|
36
|
+
if (key === 'fileReference' && child && typeof child === 'object' && !Array.isArray(child)) {
|
|
37
|
+
const ref = { ...child };
|
|
38
|
+
delete ref.url;
|
|
39
|
+
delete ref.relativePath;
|
|
40
|
+
out[key] = ref;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (fileShaped && (key === 'url' || key === 'relativePath'))
|
|
44
|
+
continue;
|
|
45
|
+
out[key] = redactFileLocators(child);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
function expandEnvRefs(value) {
|
|
50
|
+
if (!value)
|
|
51
|
+
return '';
|
|
52
|
+
return value.replace(/\$\{([A-Z][A-Z0-9_]*)\}/g, (_match, name) => process.env[name] ?? '');
|
|
53
|
+
}
|
|
54
|
+
function resolveUserId(_execution, context) {
|
|
55
|
+
// 仅用调用期身份(Token/ExecutionContext);禁止依赖 manifest 静态 headers.userId
|
|
56
|
+
const fromCtx = context?.userId?.trim();
|
|
57
|
+
return fromCtx || undefined;
|
|
58
|
+
}
|
|
59
|
+
function withUserMeta(params, userId) {
|
|
60
|
+
if (!userId)
|
|
61
|
+
return params;
|
|
62
|
+
const existingMeta = params._meta && typeof params._meta === 'object' && !Array.isArray(params._meta)
|
|
63
|
+
? params._meta
|
|
64
|
+
: {};
|
|
65
|
+
return {
|
|
66
|
+
...params,
|
|
67
|
+
_meta: { ...existingMeta, userId: existingMeta.userId ?? userId },
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function rpcStdio(session, method, params) {
|
|
71
|
+
const id = session.nextId++;
|
|
72
|
+
const timeoutMs = session.timeoutMs;
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
const timer = setTimeout(() => {
|
|
75
|
+
session.pending.delete(id);
|
|
76
|
+
reject(new Error(`MCP RPC timeout: ${method}`));
|
|
77
|
+
}, timeoutMs);
|
|
78
|
+
session.pending.set(id, {
|
|
79
|
+
resolve: (v) => { clearTimeout(timer); resolve(v); },
|
|
80
|
+
reject: (e) => { clearTimeout(timer); reject(e); },
|
|
81
|
+
});
|
|
82
|
+
session.process.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, method, params })}\n`);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async function invokeStdio(execution, args, context) {
|
|
86
|
+
const { server } = execution;
|
|
87
|
+
const command = server.command ?? 'node';
|
|
88
|
+
const spawnArgs = server.args ?? [];
|
|
89
|
+
const timeoutMs = execution.timeout ?? 30_000;
|
|
90
|
+
const userId = resolveUserId(execution, context);
|
|
91
|
+
const child = spawn(command, spawnArgs, {
|
|
92
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
93
|
+
cwd: server.cwd,
|
|
94
|
+
env: {
|
|
95
|
+
...process.env,
|
|
96
|
+
...server.env,
|
|
97
|
+
...(userId ? { EAP_MCP_USER_ID: userId } : {}),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
const session = {
|
|
101
|
+
process: child,
|
|
102
|
+
nextId: 1,
|
|
103
|
+
pending: new Map(),
|
|
104
|
+
timeoutMs,
|
|
105
|
+
};
|
|
106
|
+
const rl = readline.createInterface({ input: child.stdout });
|
|
107
|
+
rl.on('line', (line) => {
|
|
108
|
+
if (!line.trim())
|
|
109
|
+
return;
|
|
110
|
+
try {
|
|
111
|
+
const msg = JSON.parse(line);
|
|
112
|
+
if (msg.id == null)
|
|
113
|
+
return;
|
|
114
|
+
const p = session.pending.get(msg.id);
|
|
115
|
+
if (!p)
|
|
116
|
+
return;
|
|
117
|
+
session.pending.delete(msg.id);
|
|
118
|
+
if (msg.error)
|
|
119
|
+
p.reject(new Error(msg.error.message));
|
|
120
|
+
else
|
|
121
|
+
p.resolve(msg.result);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
/* ignore */
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
try {
|
|
128
|
+
await rpcStdio(session, 'initialize', {
|
|
129
|
+
protocolVersion: '2024-11-05',
|
|
130
|
+
capabilities: {},
|
|
131
|
+
clientInfo: { name: 'eap-runtime-local', version: '0.1.0' },
|
|
132
|
+
});
|
|
133
|
+
const result = await rpcStdio(session, 'tools/call', withUserMeta({ name: execution.toolName, arguments: args }, userId));
|
|
134
|
+
return toInvokeResult(result, execution.server.env);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
child.kill();
|
|
138
|
+
rl.close();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function invokeStreamableHttp(execution, args, context) {
|
|
142
|
+
const url = expandEnvRefs(execution.server.url);
|
|
143
|
+
if (!url) {
|
|
144
|
+
throw new Error('streamable_http MCP requires server.url');
|
|
145
|
+
}
|
|
146
|
+
const timeoutMs = execution.timeout ?? 30_000;
|
|
147
|
+
const controller = new AbortController();
|
|
148
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
149
|
+
const userId = resolveUserId(execution, context);
|
|
150
|
+
const rawHeaders = execution.server.headers ?? {};
|
|
151
|
+
const expandedHeaders = {};
|
|
152
|
+
for (const [key, value] of Object.entries(rawHeaders)) {
|
|
153
|
+
expandedHeaders[key] = expandEnvRefs(value);
|
|
154
|
+
}
|
|
155
|
+
const headers = {
|
|
156
|
+
'Content-Type': 'application/json',
|
|
157
|
+
Accept: 'application/json, text/event-stream',
|
|
158
|
+
...expandedHeaders,
|
|
159
|
+
};
|
|
160
|
+
// mcp-data-service 认 header `userId`(非 X-User-Id);Token 上下文可覆盖静态头
|
|
161
|
+
if (userId) {
|
|
162
|
+
headers.userId = userId;
|
|
163
|
+
}
|
|
164
|
+
const body = {
|
|
165
|
+
jsonrpc: '2.0',
|
|
166
|
+
id: 1,
|
|
167
|
+
method: 'tools/call',
|
|
168
|
+
params: withUserMeta({ name: execution.toolName, arguments: args }, userId),
|
|
169
|
+
};
|
|
170
|
+
try {
|
|
171
|
+
const response = await fetch(url, {
|
|
172
|
+
method: 'POST',
|
|
173
|
+
headers,
|
|
174
|
+
body: JSON.stringify(body),
|
|
175
|
+
signal: controller.signal,
|
|
176
|
+
});
|
|
177
|
+
if (!response.ok) {
|
|
178
|
+
throw new Error(`MCP HTTP ${response.status}: ${response.statusText}`);
|
|
179
|
+
}
|
|
180
|
+
const raw = await response.text();
|
|
181
|
+
const payload = parseMcpHttpBody(raw);
|
|
182
|
+
if (payload.error) {
|
|
183
|
+
throw new Error(payload.error.message ?? 'MCP HTTP error');
|
|
184
|
+
}
|
|
185
|
+
return toInvokeResult(payload.result, execution.server.env);
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
clearTimeout(timer);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function parseMcpHttpBody(raw) {
|
|
192
|
+
const trimmed = raw.trim();
|
|
193
|
+
if (trimmed.startsWith('{')) {
|
|
194
|
+
return JSON.parse(trimmed);
|
|
195
|
+
}
|
|
196
|
+
// SSE: 取最后一条 data 行
|
|
197
|
+
const lines = trimmed.split('\n');
|
|
198
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
199
|
+
const line = lines[i].trim();
|
|
200
|
+
if (line.startsWith('data:')) {
|
|
201
|
+
const json = line.slice(5).trim();
|
|
202
|
+
if (json) {
|
|
203
|
+
return JSON.parse(json);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
throw new Error('Unrecognized MCP HTTP response');
|
|
208
|
+
}
|
|
209
|
+
export class LocalMcpClient {
|
|
210
|
+
async invoke(execution, args, context) {
|
|
211
|
+
if (execution.transport === 'streamable_http') {
|
|
212
|
+
return invokeStreamableHttp(execution, args, context);
|
|
213
|
+
}
|
|
214
|
+
return invokeStdio(execution, args, context);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function toInvokeResult(raw, serverEnv) {
|
|
218
|
+
const structured = (raw && typeof raw === 'object' && !Array.isArray(raw))
|
|
219
|
+
? raw.structuredContent
|
|
220
|
+
: undefined;
|
|
221
|
+
return {
|
|
222
|
+
content: formatMcpOutput(raw),
|
|
223
|
+
file: fileArtifactFromToolOutput(structured ?? raw, fileBasesFromEnv(serverEnv)),
|
|
224
|
+
};
|
|
225
|
+
}
|