@gt-fe/eap-runtime 0.0.22 → 0.0.24
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/dist/api/handlers/invoke.handler.d.ts +4 -0
- package/dist/api/handlers/invoke.handler.js +38 -19
- package/dist/api/handlers/stream.handler.d.ts +4 -0
- package/dist/api/handlers/stream.handler.js +24 -15
- package/dist/api/routes/agent.routes.js +6 -5
- package/dist/checkpoint/checkpoint-ref-notify.js +1 -1
- package/dist/common/types.d.ts +6 -2
- package/dist/common/types.js +3 -1
- package/dist/graph/adapters/langgraph.adapter.d.ts +6 -4
- package/dist/graph/adapters/langgraph.adapter.js +103 -18
- package/dist/graph/contracts/executable-graph.d.ts +9 -0
- package/dist/graph/execution-trace-context.d.ts +4 -0
- package/dist/graph/execution-trace-context.js +3 -1
- package/dist/graph/nodes/rag/types.d.ts +4 -1
- package/dist/graph/nodes/tool-node.js +10 -1
- package/dist/providers/http-llm-provider.js +25 -0
- package/dist/registry/graph-loader.js +65 -9
- package/dist/runtime/agent-runtime.d.ts +3 -0
- package/dist/runtime/agent-runtime.js +95 -15
- package/dist/runtime/dev-runtime.js +9 -2
- package/dist/runtime/types.d.ts +1 -1
- package/dist/sse/events/response-events.d.ts +2 -2
- package/dist/sse/events/response-events.js +1 -1
- package/dist/task/contracts/task-manager.contract.d.ts +2 -0
- package/dist/task/index.d.ts +1 -0
- package/dist/task/index.js +1 -0
- package/dist/task/resolve-task-timeout.d.ts +6 -0
- package/dist/task/resolve-task-timeout.js +16 -0
- package/dist/task/task-timeout.d.ts +1 -1
- package/dist/task/task-timeout.js +3 -2
- package/dist/task/task.service.js +1 -0
- package/dist/tool/http-tool-executor.js +7 -3
- package/dist/tool/index.d.ts +2 -1
- package/dist/tool/index.js +2 -1
- package/dist/tool/loaded-tool-definition.d.ts +1 -1
- package/dist/tool/local-mcp-client.d.ts +1 -1
- package/dist/tool/local-mcp-client.js +2 -39
- package/dist/tool/local-tool-executor.js +32 -5
- package/dist/tool/mcp-list.d.ts +21 -0
- package/dist/tool/mcp-list.js +49 -3
- package/dist/tool/mcp-tool-executor.d.ts +1 -1
- package/dist/tool/mcp-tool-executor.js +58 -22
- package/dist/tool/model-visible-tool-content.d.ts +16 -0
- package/dist/tool/model-visible-tool-content.js +266 -0
- package/dist/tool/timeout.d.ts +5 -12
- package/dist/tool/timeout.js +12 -34
- package/dist/tool/tool-executor.interface.d.ts +7 -1
- package/dist/tool/tool-orchestrator.js +6 -1
- package/dist/tool/tool-result-injector.js +2 -1
- package/package.json +2 -2
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
* @see design.md §1.1 任务管理 — Task级超时
|
|
10
10
|
* @see requirements.md Requirement 7 AC4
|
|
11
11
|
*/
|
|
12
|
+
import { DEFAULT_GRAPH_TIMEOUT_MS } from '../common/types.js';
|
|
12
13
|
// ─────────────────────────────────────────────
|
|
13
14
|
// Default Constants
|
|
14
15
|
// ─────────────────────────────────────────────
|
|
15
|
-
/**
|
|
16
|
-
const DEFAULT_TIMEOUT_MS =
|
|
16
|
+
/** 默认超时:与图/请求缺省同一常数 */
|
|
17
|
+
const DEFAULT_TIMEOUT_MS = DEFAULT_GRAPH_TIMEOUT_MS;
|
|
17
18
|
/** 最大允许超时:3600 秒(1 小时) */
|
|
18
19
|
const MAX_TIMEOUT_MS = 3_600_000;
|
|
19
20
|
// ─────────────────────────────────────────────
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @see docs/contracts/cross-service-api.md §4.6
|
|
6
6
|
*/
|
|
7
7
|
import { ErrorCodes } from '@gt-fe/eap-contracts';
|
|
8
|
-
import { abortOrTimeoutMessage, isAbortError } from './timeout.js';
|
|
8
|
+
import { abortOrTimeoutMessage, isAbortError, timeoutMsFromExecution } from './timeout.js';
|
|
9
9
|
function mapGatewayHttpError(status, body, statusText) {
|
|
10
10
|
const code = body?.error?.code;
|
|
11
11
|
const bodyMessage = [body?.error?.message, body?.message].find((s) => typeof s === 'string' && s.trim()) ?? '';
|
|
@@ -81,13 +81,17 @@ export class HttpToolExecutor {
|
|
|
81
81
|
Authorization: `Bearer ${request.authContext.delegationToken}`,
|
|
82
82
|
'X-Agent-Id': agentCode,
|
|
83
83
|
'X-Agent-Code': agentCode,
|
|
84
|
+
...(request.authContext.agentVersion
|
|
85
|
+
? { 'X-Agent-Version': request.authContext.agentVersion }
|
|
86
|
+
: {}),
|
|
84
87
|
'X-User-Id': request.authContext.userId,
|
|
85
88
|
userId: request.authContext.userId,
|
|
86
89
|
'X-Correlation-Id': correlationId,
|
|
87
90
|
'X-Task-Id': taskId,
|
|
88
91
|
};
|
|
89
92
|
const controller = new AbortController();
|
|
90
|
-
const
|
|
93
|
+
const timeoutMs = timeoutMsFromExecution(request.execution, this.timeout);
|
|
94
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
91
95
|
try {
|
|
92
96
|
const response = await fetch(url, {
|
|
93
97
|
method: 'POST',
|
|
@@ -137,7 +141,7 @@ export class HttpToolExecutor {
|
|
|
137
141
|
}
|
|
138
142
|
catch (error) {
|
|
139
143
|
clearTimeout(timeoutId);
|
|
140
|
-
const timeoutSec = Math.max(1, Math.round(this.timeout / 1000));
|
|
144
|
+
const timeoutSec = Math.max(1, Math.round(timeoutMsFromExecution(request.execution, this.timeout) / 1000));
|
|
141
145
|
const aborted = isAbortError(error);
|
|
142
146
|
return {
|
|
143
147
|
toolCallId: request.toolCallId,
|
package/dist/tool/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { AuthContext, ToolCallRequest, ToolCallResult, LLMToolCall, LLMToolCallResponse, IToolExecutor, McpListedTool, } from './tool-executor.interface.js';
|
|
2
|
-
export { hydrateMcpTools, isMcpDiscoverStub, needsMcpHydration, parseListedTools, toolsetCodeFromLoaded, toolTraceIdentity, } from './mcp-list.js';
|
|
2
|
+
export { assertScopedListEcho, hydrateMcpTools, isMcpDiscoverStub, mcpToolsetCode, needsMcpHydration, parseListedTools, toolsetCodeFromLoaded, toolTraceIdentity, } from './mcp-list.js';
|
|
3
3
|
export { toLoadedToolMap, type LoadedToolDefinition, type LoadedToolMap, type LoadedToolManifestRef, } from './loaded-tool-definition.js';
|
|
4
4
|
export { ToolOrchestrator } from './tool-orchestrator.js';
|
|
5
5
|
export { isRetryableToolFailure, resolveToolRetryConfig, type ToolFailureShape, } from './tool-retry.js';
|
|
@@ -11,6 +11,7 @@ export { DevHttpExecutor, buildHttpToolUrl, resolveHttpExecutionFromRequest, typ
|
|
|
11
11
|
export { DevToolExecutor, type DevToolExecutorOptions } from './dev-tool-executor.js';
|
|
12
12
|
export { LocalMcpClient, type ILocalMcpClient, type LocalMcpInvokeContext, type LocalMcpInvokeResult, } from './local-mcp-client.js';
|
|
13
13
|
export { fileArtifactFromToolOutput, extractFileReference, resolveFileArtifact, type FileArtifact, } from './file-artifact.js';
|
|
14
|
+
export { formatMcpOutput, toModelVisibleToolContent, MAX_MODEL_RESULT_ROWS, MAX_MODEL_CONTENT_CHARS, MAX_MODEL_RESULT_CHARS, } from './model-visible-tool-content.js';
|
|
14
15
|
export { ToolExecutionRouter, type DeploymentContext, type ToolExecutionRouterOptions, } from './tool-execution-router.js';
|
|
15
16
|
export { createToolExecutor, EmptyInstallStore, type CreateToolExecutorOptions, } from './create-tool-executor.js';
|
|
16
17
|
export { assembleLoadedTools, mergeHarnessPrimitives as mergeBuiltinTools, withHarnessPrimitives as withBuiltinTools, HarnessPrimitiveExecutor as BuiltinToolExecutor, resolveUserDir, resolveJailFromState, captureOverlayJailOptions, collectToolBundlePaths, type HarnessJailOptions as BuiltinJailOptions, } from './harness-primitives.js';
|
package/dist/tool/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { hydrateMcpTools, isMcpDiscoverStub, needsMcpHydration, parseListedTools, toolsetCodeFromLoaded, toolTraceIdentity, } from './mcp-list.js';
|
|
1
|
+
export { assertScopedListEcho, hydrateMcpTools, isMcpDiscoverStub, mcpToolsetCode, needsMcpHydration, parseListedTools, toolsetCodeFromLoaded, toolTraceIdentity, } from './mcp-list.js';
|
|
2
2
|
export { toLoadedToolMap, } from './loaded-tool-definition.js';
|
|
3
3
|
export { ToolOrchestrator } from './tool-orchestrator.js';
|
|
4
4
|
export { isRetryableToolFailure, resolveToolRetryConfig, } from './tool-retry.js';
|
|
@@ -10,6 +10,7 @@ export { DevHttpExecutor, buildHttpToolUrl, resolveHttpExecutionFromRequest, } f
|
|
|
10
10
|
export { DevToolExecutor } from './dev-tool-executor.js';
|
|
11
11
|
export { LocalMcpClient, } from './local-mcp-client.js';
|
|
12
12
|
export { fileArtifactFromToolOutput, extractFileReference, resolveFileArtifact, } from './file-artifact.js';
|
|
13
|
+
export { formatMcpOutput, toModelVisibleToolContent, MAX_MODEL_RESULT_ROWS, MAX_MODEL_CONTENT_CHARS, MAX_MODEL_RESULT_CHARS, } from './model-visible-tool-content.js';
|
|
13
14
|
export { ToolExecutionRouter, } from './tool-execution-router.js';
|
|
14
15
|
export { createToolExecutor, EmptyInstallStore, } from './create-tool-executor.js';
|
|
15
16
|
export { assembleLoadedTools, mergeHarnessPrimitives as mergeBuiltinTools, withHarnessPrimitives as withBuiltinTools, HarnessPrimitiveExecutor as BuiltinToolExecutor, resolveUserDir, resolveJailFromState, captureOverlayJailOptions, collectToolBundlePaths, } from './harness-primitives.js';
|
|
@@ -21,7 +21,7 @@ export interface LoadedToolManifestRef {
|
|
|
21
21
|
* MCP/HTTP 出站执行配置(来自 tool.manifest.execution)
|
|
22
22
|
*
|
|
23
23
|
* dev 上下文 DevMcpExecutor 消费此字段做直连(stdio / streamable_http / sse);
|
|
24
|
-
* server 上下文
|
|
24
|
+
* server 上下文 Gateway 按 `params._meta.toolsetCode` / `version` 分派,不读清单 server。
|
|
25
25
|
*/
|
|
26
26
|
execution?: unknown;
|
|
27
27
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { McpRuntimeExecution } from '@gt-fe/eap-contracts';
|
|
8
8
|
import { type FileArtifact } from './file-artifact.js';
|
|
9
9
|
import type { McpListedTool } from './tool-executor.interface.js';
|
|
10
|
+
export { formatMcpOutput } from './model-visible-tool-content.js';
|
|
10
11
|
export interface LocalMcpInvokeContext {
|
|
11
12
|
/** Platform Token.sub / ExecutionContext.userId */
|
|
12
13
|
userId?: string | null;
|
|
@@ -19,7 +20,6 @@ export interface ILocalMcpClient {
|
|
|
19
20
|
invoke(execution: McpRuntimeExecution, args: Record<string, unknown>, context?: LocalMcpInvokeContext): Promise<LocalMcpInvokeResult>;
|
|
20
21
|
list(execution: McpRuntimeExecution, context?: LocalMcpInvokeContext): Promise<McpListedTool[]>;
|
|
21
22
|
}
|
|
22
|
-
export declare function formatMcpOutput(result: unknown): string;
|
|
23
23
|
export declare class LocalMcpClient implements ILocalMcpClient {
|
|
24
24
|
invoke(execution: McpRuntimeExecution, args: Record<string, unknown>, context?: LocalMcpInvokeContext): Promise<LocalMcpInvokeResult>;
|
|
25
25
|
list(execution: McpRuntimeExecution, context?: LocalMcpInvokeContext): Promise<McpListedTool[]>;
|
|
@@ -8,46 +8,9 @@ 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 { formatMcpOutput } from './model-visible-tool-content.js';
|
|
11
12
|
import { nextListCursor, parseListedTools } from './mcp-list.js';
|
|
12
|
-
export
|
|
13
|
-
const r = result;
|
|
14
|
-
if (r?.structuredContent !== undefined) {
|
|
15
|
-
return JSON.stringify(redactFileLocators(r.structuredContent));
|
|
16
|
-
}
|
|
17
|
-
const text = r?.content?.[0]?.text;
|
|
18
|
-
if (text) {
|
|
19
|
-
try {
|
|
20
|
-
return JSON.stringify(redactFileLocators(JSON.parse(text)));
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
return text;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return JSON.stringify(redactFileLocators(result));
|
|
27
|
-
}
|
|
28
|
-
function redactFileLocators(value) {
|
|
29
|
-
if (Array.isArray(value))
|
|
30
|
-
return value.map(redactFileLocators);
|
|
31
|
-
if (!value || typeof value !== 'object')
|
|
32
|
-
return value;
|
|
33
|
-
const obj = value;
|
|
34
|
-
const fileShaped = typeof obj.fileName === 'string'
|
|
35
|
-
&& (typeof obj.url === 'string' || typeof obj.relativePath === 'string');
|
|
36
|
-
const out = {};
|
|
37
|
-
for (const [key, child] of Object.entries(obj)) {
|
|
38
|
-
if (key === 'fileReference' && child && typeof child === 'object' && !Array.isArray(child)) {
|
|
39
|
-
const ref = { ...child };
|
|
40
|
-
delete ref.url;
|
|
41
|
-
delete ref.relativePath;
|
|
42
|
-
out[key] = ref;
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
if (fileShaped && (key === 'url' || key === 'relativePath'))
|
|
46
|
-
continue;
|
|
47
|
-
out[key] = redactFileLocators(child);
|
|
48
|
-
}
|
|
49
|
-
return out;
|
|
50
|
-
}
|
|
13
|
+
export { formatMcpOutput } from './model-visible-tool-content.js';
|
|
51
14
|
function expandEnvRefs(value) {
|
|
52
15
|
if (!value)
|
|
53
16
|
return '';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 或 request.execution 带可解析的 code bundlePath+handler,则直接本机执行。
|
|
6
6
|
*/
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
|
-
import { existsSync } from 'node:fs';
|
|
8
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { ErrorCodes, resolveReadyInstallByToolName, resolveMcpExecutionFromInstall, resolveCodeHandlerFromInstall, } from '@gt-fe/eap-contracts';
|
|
11
11
|
import { isAbortError, toToolTimeoutMs, toolTimeoutErrorMessage } from './timeout.js';
|
|
@@ -49,13 +49,21 @@ function formatCodeLaunchError(err, spec) {
|
|
|
49
49
|
}
|
|
50
50
|
return new Error(err instanceof Error ? err.message : String(err));
|
|
51
51
|
}
|
|
52
|
-
function runHandler(handlerPath, input, timeoutMs, execution) {
|
|
52
|
+
function runHandler(handlerPath, input, timeoutMs, execution, spawnOpts) {
|
|
53
53
|
if (!existsSync(handlerPath)) {
|
|
54
54
|
return Promise.reject(new Error(`找不到 handler 文件:${handlerPath}。请检查 toolset.manifest.json 的 tools[].handler 与工具目录。`));
|
|
55
55
|
}
|
|
56
56
|
const { cmd, args } = resolveCommand(handlerPath, execution);
|
|
57
|
+
const cwd = spawnOpts?.cwd?.trim() || undefined;
|
|
58
|
+
if (cwd)
|
|
59
|
+
mkdirSync(cwd, { recursive: true });
|
|
60
|
+
const env = { ...process.env, ...(spawnOpts?.env ?? {}) };
|
|
61
|
+
if (spawnOpts?.userId)
|
|
62
|
+
env.EAP_USER_ID = spawnOpts.userId;
|
|
63
|
+
if (cwd)
|
|
64
|
+
env.EAP_ARTIFACT_ROOT = cwd;
|
|
57
65
|
return new Promise((resolve, reject) => {
|
|
58
|
-
const child = spawn(cmd, args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
66
|
+
const child = spawn(cmd, args, { stdio: ['pipe', 'pipe', 'pipe'], cwd, env });
|
|
59
67
|
let stdout = '';
|
|
60
68
|
let stderr = '';
|
|
61
69
|
let settled = false;
|
|
@@ -92,6 +100,20 @@ function runHandler(handlerPath, input, timeoutMs, execution) {
|
|
|
92
100
|
child.stdin.end();
|
|
93
101
|
});
|
|
94
102
|
}
|
|
103
|
+
function executionEnvFromRequest(request) {
|
|
104
|
+
const raw = request.execution;
|
|
105
|
+
if (!raw || typeof raw !== 'object')
|
|
106
|
+
return undefined;
|
|
107
|
+
const env = raw.env;
|
|
108
|
+
if (!env || typeof env !== 'object' || Array.isArray(env))
|
|
109
|
+
return undefined;
|
|
110
|
+
const out = {};
|
|
111
|
+
for (const [key, value] of Object.entries(env)) {
|
|
112
|
+
if (typeof value === 'string')
|
|
113
|
+
out[key] = value;
|
|
114
|
+
}
|
|
115
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
116
|
+
}
|
|
95
117
|
/** 是否为应本机执行的 code(显式 localPreferred,例如 DevToolExecutor) */
|
|
96
118
|
export function isLocalPreferredExecution(raw) {
|
|
97
119
|
if (!raw || typeof raw !== 'object')
|
|
@@ -188,14 +210,19 @@ export class LocalToolExecutor {
|
|
|
188
210
|
}
|
|
189
211
|
async executeCodeSpec(request, spec) {
|
|
190
212
|
try {
|
|
191
|
-
const
|
|
213
|
+
const jail = request.jailWorkdir?.trim() || undefined;
|
|
214
|
+
const stdout = await runHandler(spec.handlerPath, request.arguments, spec.timeoutMs ?? this.options.timeoutMs ?? 30_000, { runtime: spec.runtime, args: spec.args }, {
|
|
215
|
+
cwd: jail,
|
|
216
|
+
userId: request.authContext?.userId,
|
|
217
|
+
env: executionEnvFromRequest(request),
|
|
218
|
+
});
|
|
192
219
|
return {
|
|
193
220
|
toolCallId: request.toolCallId,
|
|
194
221
|
toolName: request.toolName,
|
|
195
222
|
content: stdout,
|
|
196
223
|
success: true,
|
|
197
224
|
runtime: 'local',
|
|
198
|
-
file: fileArtifactFromToolOutput(stdout, fileBasesFromEnv()),
|
|
225
|
+
file: fileArtifactFromToolOutput(stdout, [...fileBasesFromEnv(), jail]),
|
|
199
226
|
};
|
|
200
227
|
}
|
|
201
228
|
catch (error) {
|
package/dist/tool/mcp-list.d.ts
CHANGED
|
@@ -8,6 +8,14 @@ import type { LoadedToolDefinition, LoadedToolMap } from './loaded-tool-definiti
|
|
|
8
8
|
import type { AuthContext, IToolExecutor, McpListedTool } from './tool-executor.interface.js';
|
|
9
9
|
export declare function parseListedTools(result: unknown): McpListedTool[];
|
|
10
10
|
export declare function nextListCursor(result: unknown): string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* 有 toolsetCode 的 tools/list:回包 `_meta` 必须与请求作用域一致(Gateway A16)。
|
|
13
|
+
* 无 `_meta` 或编码/版本不符视为本次 list 失败,不把全局目录当该工具集。
|
|
14
|
+
*/
|
|
15
|
+
export declare function assertScopedListEcho(result: unknown, expected: {
|
|
16
|
+
toolsetCode: string;
|
|
17
|
+
version?: string;
|
|
18
|
+
}): void;
|
|
11
19
|
export interface McpDiscoverExecution {
|
|
12
20
|
protocol?: string;
|
|
13
21
|
discover?: boolean;
|
|
@@ -32,6 +40,19 @@ export declare function toolsetCodeFromLoaded(loaded: {
|
|
|
32
40
|
id?: string;
|
|
33
41
|
};
|
|
34
42
|
} | undefined): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Gateway `params._meta.toolsetCode`:hydrate 后的 `toolset:toolName` 前缀,
|
|
45
|
+
* 或 MCP discover stub 自身的工具集编码。
|
|
46
|
+
*/
|
|
47
|
+
export declare function mcpToolsetCode(loaded: {
|
|
48
|
+
toolId?: string;
|
|
49
|
+
name?: string;
|
|
50
|
+
manifest?: {
|
|
51
|
+
id?: string;
|
|
52
|
+
type?: string;
|
|
53
|
+
execution?: unknown;
|
|
54
|
+
};
|
|
55
|
+
} | undefined): string | undefined;
|
|
35
56
|
/** Tool Span:工具集 code + 声明版本。与 Skill 的 skillSpace/version 同一假设。 */
|
|
36
57
|
export declare function toolTraceIdentity(loaded: {
|
|
37
58
|
toolId?: string;
|
package/dist/tool/mcp-list.js
CHANGED
|
@@ -16,8 +16,9 @@ export function parseListedTools(result) {
|
|
|
16
16
|
const t = raw;
|
|
17
17
|
if (typeof t.name !== 'string' || !t.name.trim())
|
|
18
18
|
continue;
|
|
19
|
-
const
|
|
20
|
-
|
|
19
|
+
const schemaRaw = t.inputSchema ?? t.input;
|
|
20
|
+
const inputSchema = schemaRaw && typeof schemaRaw === 'object' && !Array.isArray(schemaRaw)
|
|
21
|
+
? schemaRaw
|
|
21
22
|
: undefined;
|
|
22
23
|
out.push({
|
|
23
24
|
name: t.name,
|
|
@@ -31,6 +32,30 @@ export function nextListCursor(result) {
|
|
|
31
32
|
const cursor = result?.nextCursor;
|
|
32
33
|
return typeof cursor === 'string' && cursor.trim() ? cursor : undefined;
|
|
33
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* 有 toolsetCode 的 tools/list:回包 `_meta` 必须与请求作用域一致(Gateway A16)。
|
|
37
|
+
* 无 `_meta` 或编码/版本不符视为本次 list 失败,不把全局目录当该工具集。
|
|
38
|
+
*/
|
|
39
|
+
export function assertScopedListEcho(result, expected) {
|
|
40
|
+
const root = result && typeof result === 'object' && !Array.isArray(result)
|
|
41
|
+
? result
|
|
42
|
+
: {};
|
|
43
|
+
const echo = root._meta && typeof root._meta === 'object' && !Array.isArray(root._meta)
|
|
44
|
+
? root._meta
|
|
45
|
+
: {};
|
|
46
|
+
const code = typeof echo.toolsetCode === 'string' ? echo.toolsetCode.trim() : '';
|
|
47
|
+
const version = typeof echo.version === 'string' ? echo.version.trim() : '';
|
|
48
|
+
const wantCode = expected.toolsetCode.trim();
|
|
49
|
+
const wantVersion = expected.version?.trim() ?? '';
|
|
50
|
+
if (!wantCode)
|
|
51
|
+
return;
|
|
52
|
+
if (code !== wantCode) {
|
|
53
|
+
throw new Error(`MCP tools/list 回包 toolsetCode 与请求不一致: 期望 ${wantCode}, 实际 ${code || '<missing>'}`);
|
|
54
|
+
}
|
|
55
|
+
if (wantVersion && version !== wantVersion) {
|
|
56
|
+
throw new Error(`MCP tools/list 回包 version 与请求不一致: 期望 ${wantVersion}, 实际 ${version || '<missing>'}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
34
59
|
export function readMcpDiscoverExecution(raw) {
|
|
35
60
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
36
61
|
return null;
|
|
@@ -84,6 +109,21 @@ export function toolsetCodeFromLoaded(loaded) {
|
|
|
84
109
|
}
|
|
85
110
|
return undefined;
|
|
86
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Gateway `params._meta.toolsetCode`:hydrate 后的 `toolset:toolName` 前缀,
|
|
114
|
+
* 或 MCP discover stub 自身的工具集编码。
|
|
115
|
+
*/
|
|
116
|
+
export function mcpToolsetCode(loaded) {
|
|
117
|
+
const fromPrefix = toolsetCodeFromLoaded(loaded);
|
|
118
|
+
if (fromPrefix)
|
|
119
|
+
return fromPrefix;
|
|
120
|
+
if (!loaded || loaded.manifest?.type !== 'mcp')
|
|
121
|
+
return undefined;
|
|
122
|
+
if (!readMcpDiscoverExecution(loaded.manifest.execution))
|
|
123
|
+
return undefined;
|
|
124
|
+
const id = (loaded.manifest.id || loaded.toolId || loaded.name || '').trim();
|
|
125
|
+
return id || undefined;
|
|
126
|
+
}
|
|
87
127
|
function declaredToolVersion(toolName, version) {
|
|
88
128
|
const raw = version?.trim();
|
|
89
129
|
if (!raw || raw === toolName)
|
|
@@ -120,7 +160,10 @@ export async function hydrateMcpTools(loadedTools, executor, auth) {
|
|
|
120
160
|
}
|
|
121
161
|
const groups = new Map();
|
|
122
162
|
for (const stub of stubs) {
|
|
123
|
-
const
|
|
163
|
+
const endpoint = mcpEndpointKey(stub.manifest.execution);
|
|
164
|
+
const toolsetCode = mcpToolsetCode(stub) ?? stub.name;
|
|
165
|
+
const version = stub.manifest.version?.trim() ?? '';
|
|
166
|
+
const key = `${endpoint}\0${toolsetCode}\0${version}`;
|
|
124
167
|
const list = groups.get(key);
|
|
125
168
|
if (list)
|
|
126
169
|
list.push(stub);
|
|
@@ -132,6 +175,7 @@ export async function hydrateMcpTools(loadedTools, executor, auth) {
|
|
|
132
175
|
for (const [, group] of groups) {
|
|
133
176
|
const primary = group[0];
|
|
134
177
|
const endpoint = mcpEndpointKey(primary.manifest.execution, primary.name);
|
|
178
|
+
const toolsetCode = mcpToolsetCode(primary);
|
|
135
179
|
let listed;
|
|
136
180
|
try {
|
|
137
181
|
listed = await executor.listTools({
|
|
@@ -144,6 +188,8 @@ export async function hydrateMcpTools(loadedTools, executor, auth) {
|
|
|
144
188
|
toolType: 'mcp',
|
|
145
189
|
toolRuntime: primary.manifest.toolRuntime,
|
|
146
190
|
execution: primary.manifest.execution,
|
|
191
|
+
...(toolsetCode ? { toolsetCode } : {}),
|
|
192
|
+
...(primary.manifest.version ? { toolVersion: primary.manifest.version } : {}),
|
|
147
193
|
});
|
|
148
194
|
}
|
|
149
195
|
catch (err) {
|
|
@@ -18,7 +18,7 @@ export declare class McpToolExecutor implements IToolExecutor {
|
|
|
18
18
|
private readonly mcpUrl;
|
|
19
19
|
private rpcId;
|
|
20
20
|
private initialized;
|
|
21
|
-
/** endpoint+user+agent → in-flight / completed tools/list */
|
|
21
|
+
/** endpoint+user+agent+toolset+version → in-flight / completed tools/list */
|
|
22
22
|
private readonly listCache;
|
|
23
23
|
constructor(options: McpToolExecutorOptions);
|
|
24
24
|
execute(request: ToolCallRequest): Promise<ToolCallResult>;
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { ErrorCodes } from '@gt-fe/eap-contracts';
|
|
7
7
|
import { emitRuntimeLog } from '../common/logger.js';
|
|
8
|
-
import { abortOrTimeoutMessage, isAbortError } from './timeout.js';
|
|
9
|
-
import {
|
|
8
|
+
import { abortOrTimeoutMessage, isAbortError, timeoutMsFromExecution } from './timeout.js';
|
|
9
|
+
import { formatMcpOutput } from './model-visible-tool-content.js';
|
|
10
|
+
import { fileArtifactFromToolOutput, fileBasesFromEnv } from './file-artifact.js';
|
|
11
|
+
import { assertScopedListEcho, nextListCursor, parseListedTools } from './mcp-list.js';
|
|
10
12
|
/** Tool Gateway 基址:必须是绝对 http(s)。空串 / 相对路径禁止 fetch。 */
|
|
11
13
|
export function isAbsoluteHttpUrl(value) {
|
|
12
14
|
if (typeof value !== 'string')
|
|
@@ -26,10 +28,7 @@ function resolveAgentCode(request) {
|
|
|
26
28
|
return request.authContext.agentCode;
|
|
27
29
|
}
|
|
28
30
|
function formatContent(result) {
|
|
29
|
-
|
|
30
|
-
if (text != null)
|
|
31
|
-
return text;
|
|
32
|
-
return JSON.stringify(result ?? {});
|
|
31
|
+
return formatMcpOutput(result ?? {});
|
|
33
32
|
}
|
|
34
33
|
function mapMcpError(status, body) {
|
|
35
34
|
const eapCode = body?.error?.data?.eapCode;
|
|
@@ -56,13 +55,41 @@ function mapMcpError(status, body) {
|
|
|
56
55
|
}
|
|
57
56
|
return { error: message, errorCode: eapCode ?? 'EXECUTION_FAILED', retryable: false };
|
|
58
57
|
}
|
|
58
|
+
/** Gateway 作用域只在 params._meta;name / arguments / cursor 是协议字段。 */
|
|
59
|
+
function mcpScopeMeta(request) {
|
|
60
|
+
const toolsetCode = request.toolsetCode?.trim();
|
|
61
|
+
const version = request.toolVersion?.trim();
|
|
62
|
+
const meta = {};
|
|
63
|
+
if (toolsetCode)
|
|
64
|
+
meta.toolsetCode = toolsetCode;
|
|
65
|
+
if (version)
|
|
66
|
+
meta.version = version;
|
|
67
|
+
return Object.keys(meta).length > 0 ? meta : undefined;
|
|
68
|
+
}
|
|
69
|
+
function mcpCallParams(request) {
|
|
70
|
+
const _meta = mcpScopeMeta(request);
|
|
71
|
+
return {
|
|
72
|
+
name: request.toolName,
|
|
73
|
+
arguments: request.arguments,
|
|
74
|
+
...(_meta ? { _meta } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function mcpListCacheKey(mcpUrl, request) {
|
|
78
|
+
return [
|
|
79
|
+
mcpUrl,
|
|
80
|
+
request.authContext.userId ?? '',
|
|
81
|
+
request.authContext.agentCode,
|
|
82
|
+
request.toolsetCode?.trim() ?? '',
|
|
83
|
+
request.toolVersion?.trim() ?? '',
|
|
84
|
+
].join('\0');
|
|
85
|
+
}
|
|
59
86
|
export class McpToolExecutor {
|
|
60
87
|
baseUrl;
|
|
61
88
|
timeout;
|
|
62
89
|
mcpUrl;
|
|
63
90
|
rpcId = 1;
|
|
64
91
|
initialized = false;
|
|
65
|
-
/** endpoint+user+agent → in-flight / completed tools/list */
|
|
92
|
+
/** endpoint+user+agent+toolset+version → in-flight / completed tools/list */
|
|
66
93
|
listCache = new Map();
|
|
67
94
|
constructor(options) {
|
|
68
95
|
if (!isAbsoluteHttpUrl(options.baseUrl)) {
|
|
@@ -87,16 +114,7 @@ export class McpToolExecutor {
|
|
|
87
114
|
jsonrpc: '2.0',
|
|
88
115
|
id,
|
|
89
116
|
method: 'tools/call',
|
|
90
|
-
params:
|
|
91
|
-
name: request.toolName,
|
|
92
|
-
arguments: request.arguments,
|
|
93
|
-
...(request.toolVersion
|
|
94
|
-
? {
|
|
95
|
-
version: request.toolVersion,
|
|
96
|
-
_meta: { version: request.toolVersion },
|
|
97
|
-
}
|
|
98
|
-
: {}),
|
|
99
|
-
},
|
|
117
|
+
params: mcpCallParams(request),
|
|
100
118
|
}, request, agentCode, correlationId);
|
|
101
119
|
if (!response.ok) {
|
|
102
120
|
let body = null;
|
|
@@ -128,16 +146,18 @@ export class McpToolExecutor {
|
|
|
128
146
|
...mapped,
|
|
129
147
|
};
|
|
130
148
|
}
|
|
149
|
+
const raw = body.result;
|
|
131
150
|
return {
|
|
132
151
|
toolCallId: request.toolCallId,
|
|
133
152
|
toolName: request.toolName,
|
|
134
|
-
content: formatContent(
|
|
153
|
+
content: formatContent(raw),
|
|
135
154
|
success: true,
|
|
136
155
|
runtime: 'server',
|
|
156
|
+
file: fileArtifactFromToolOutput(raw?.structuredContent ?? raw?.content?.[0]?.text ?? raw, [...fileBasesFromEnv(), request.jailWorkdir]),
|
|
137
157
|
};
|
|
138
158
|
}
|
|
139
159
|
catch (err) {
|
|
140
|
-
const timeoutSec = Math.max(1, Math.round(this.timeout / 1000));
|
|
160
|
+
const timeoutSec = Math.max(1, Math.round(timeoutMsFromExecution(request.execution, this.timeout) / 1000));
|
|
141
161
|
const aborted = isAbortError(err);
|
|
142
162
|
return {
|
|
143
163
|
toolCallId: request.toolCallId,
|
|
@@ -152,7 +172,7 @@ export class McpToolExecutor {
|
|
|
152
172
|
}
|
|
153
173
|
}
|
|
154
174
|
async listTools(request) {
|
|
155
|
-
const key =
|
|
175
|
+
const key = mcpListCacheKey(this.mcpUrl, request);
|
|
156
176
|
const cached = this.listCache.get(key);
|
|
157
177
|
if (cached)
|
|
158
178
|
return cached;
|
|
@@ -168,10 +188,22 @@ export class McpToolExecutor {
|
|
|
168
188
|
}
|
|
169
189
|
async listAllTools(request) {
|
|
170
190
|
await this.ensureInitialized(request);
|
|
191
|
+
const meta = mcpScopeMeta(request);
|
|
192
|
+
if (meta?.toolsetCode) {
|
|
193
|
+
const result = await this.rpcResult(request, 'tools/list', { _meta: meta });
|
|
194
|
+
assertScopedListEcho(result, {
|
|
195
|
+
toolsetCode: meta.toolsetCode,
|
|
196
|
+
version: meta.version,
|
|
197
|
+
});
|
|
198
|
+
return parseListedTools(result);
|
|
199
|
+
}
|
|
171
200
|
const tools = [];
|
|
172
201
|
let cursor;
|
|
173
202
|
do {
|
|
174
|
-
const result = await this.rpcResult(request, 'tools/list',
|
|
203
|
+
const result = await this.rpcResult(request, 'tools/list', {
|
|
204
|
+
...(cursor ? { cursor } : {}),
|
|
205
|
+
...(meta ? { _meta: meta } : {}),
|
|
206
|
+
});
|
|
175
207
|
tools.push(...parseListedTools(result));
|
|
176
208
|
cursor = nextListCursor(result);
|
|
177
209
|
} while (cursor);
|
|
@@ -223,7 +255,8 @@ export class McpToolExecutor {
|
|
|
223
255
|
}
|
|
224
256
|
async postRpc(payload, request, agentCode, correlationId) {
|
|
225
257
|
const controller = new AbortController();
|
|
226
|
-
const
|
|
258
|
+
const timeoutMs = timeoutMsFromExecution(request.execution, this.timeout);
|
|
259
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
227
260
|
try {
|
|
228
261
|
return await fetch(this.mcpUrl, {
|
|
229
262
|
method: 'POST',
|
|
@@ -234,6 +267,9 @@ export class McpToolExecutor {
|
|
|
234
267
|
Authorization: `Bearer ${request.authContext.delegationToken}`,
|
|
235
268
|
'X-Agent-Code': agentCode,
|
|
236
269
|
'X-Agent-Id': agentCode,
|
|
270
|
+
...(request.authContext.agentVersion
|
|
271
|
+
? { 'X-Agent-Version': request.authContext.agentVersion }
|
|
272
|
+
: {}),
|
|
237
273
|
'X-User-Id': request.authContext.userId,
|
|
238
274
|
// 上游 mcp-data-service 认 `userId`;Gateway 侧亦保留 X-User-Id
|
|
239
275
|
userId: request.authContext.userId,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模型可见 Tool 结果:MCP / Code 成功正文在写入 GraphState 前统一裁到最多 200 行,并写 recordCount / previewCount / truncated。
|
|
3
|
+
*
|
|
4
|
+
* 假设:模型看到的永远可能是预览;全量条数与是否裁过只在这一处写。
|
|
5
|
+
* 不按工具名分支。fileReference 路径仍由 format 阶段剔除,下载走 result.file。
|
|
6
|
+
*
|
|
7
|
+
* 预览行数按 DeepSeek V4 Pro(约 1M 上下文)估:单次工具结果要够看列和分布,
|
|
8
|
+
* 又不能看起来像全表。200 行约 3–8 万 token(视列宽);多轮 5–6 次查询仍远低于窗口。
|
|
9
|
+
* 50 行只够看样例。1000 行接近聚合工具自己的预览上限,模型容易拿来排名。
|
|
10
|
+
* 超宽表先按行裁,再按体积把行数压到整段 JSON 仍合法。
|
|
11
|
+
*/
|
|
12
|
+
export declare const MAX_MODEL_RESULT_ROWS = 200;
|
|
13
|
+
export declare const MAX_MODEL_CONTENT_CHARS = 64000;
|
|
14
|
+
export declare const MAX_MODEL_RESULT_CHARS = 120000;
|
|
15
|
+
export declare function formatMcpOutput(result: unknown): string;
|
|
16
|
+
export declare function toModelVisibleToolContent(content: string): string;
|