@gt-fe/eap-runtime 0.0.21 → 0.0.23
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 +1 -1
- package/dist/api/handlers/invoke.handler.js +6 -3
- package/dist/api/handlers/stream.handler.js +6 -2
- package/dist/checkpoint/file-checkpoint-saver.d.ts +4 -0
- package/dist/checkpoint/file-checkpoint-saver.js +51 -22
- package/dist/checkpoint/index.d.ts +1 -1
- package/dist/checkpoint/index.js +1 -1
- package/dist/graph/adapters/langgraph-checkpointer.d.ts +11 -0
- package/dist/graph/adapters/langgraph-checkpointer.js +48 -9
- package/dist/graph/adapters/langgraph.adapter.d.ts +8 -0
- package/dist/graph/adapters/langgraph.adapter.js +58 -16
- package/dist/graph/adapters/skill-isolate.d.ts +1 -5
- package/dist/graph/adapters/skill-isolate.js +1 -12
- package/dist/graph/adapters/subgraph-compiler.js +12 -15
- package/dist/graph/contracts/executable-graph.d.ts +6 -0
- package/dist/graph/hitl/hitl-handler.d.ts +13 -4
- package/dist/graph/hitl/hitl-handler.js +78 -7
- package/dist/graph/nodes/tool-node.js +11 -6
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.js +1 -1
- package/dist/providers/local-llm-provider.d.ts +6 -0
- package/dist/providers/local-llm-provider.js +29 -5
- package/dist/providers/routing-llm-provider.d.ts +2 -2
- package/dist/providers/routing-llm-provider.js +5 -15
- package/dist/registry/deployment-snapshot.js +18 -4
- package/dist/registry/graph-loader.d.ts +2 -0
- package/dist/registry/graph-loader.js +99 -7
- package/dist/registry/preload-flags.d.ts +1 -1
- package/dist/registry/preload-flags.js +1 -1
- package/dist/runtime/agent-runtime.js +27 -10
- package/dist/runtime/dev-runtime.d.ts +2 -1
- package/dist/runtime/dev-runtime.js +9 -3
- package/dist/session/apply-session-compression.d.ts +4 -0
- package/dist/session/apply-session-compression.js +22 -3
- package/dist/session/index.d.ts +1 -1
- package/dist/session/index.js +1 -1
- package/dist/session/persist-turn-messages.d.ts +6 -1
- package/dist/session/persist-turn-messages.js +23 -5
- package/dist/token/record-task-token-usage.d.ts +23 -0
- package/dist/token/record-task-token-usage.js +20 -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-confirmation.js +8 -5
- 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
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
* @see design.md §1.4 子图嵌套: Skill作为子图嵌入主Graph
|
|
9
9
|
* @see requirements.md Requirement 1 AC2 — 子图嵌套(Skill作为子图)
|
|
10
10
|
*/
|
|
11
|
-
import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
|
|
12
11
|
import { assertSkillPermission } from '../../governance/permission-guard.js';
|
|
13
12
|
import { LangGraphAdapter } from './langgraph.adapter.js';
|
|
14
13
|
import { assertSkillGraphHasNoSubgraphNodes } from './graph-nesting.js';
|
|
@@ -18,7 +17,7 @@ import { filterLoadedToolsForSkill, } from '../../tool/system-prompt-builder.js'
|
|
|
18
17
|
import { unwrapHitlPaused } from '../hitl/hitl-handler.js';
|
|
19
18
|
import { pickContextFields } from '../../token/context-window.js';
|
|
20
19
|
import { assertSkillIoIfEnabled, coerceSkillInputFromMessages, } from '../../registry/skill-io-guard.js';
|
|
21
|
-
import { isolatedSkillMessages, isolatedSkillOutput,
|
|
20
|
+
import { isolatedSkillMessages, isolatedSkillOutput, } from './skill-isolate.js';
|
|
22
21
|
/**
|
|
23
22
|
* SubGraphCompiler — 子图编译器
|
|
24
23
|
*
|
|
@@ -150,15 +149,15 @@ export class SubGraphCompiler {
|
|
|
150
149
|
: parentMessages;
|
|
151
150
|
const subGraphState = {
|
|
152
151
|
messages: subMessagesIn,
|
|
153
|
-
//
|
|
152
|
+
// 同父 session;checkpointNs=父节点 id → 子图槽 thread_id#父节点id(HITL 同槽,含 isolate)。
|
|
154
153
|
replaceCheckpointMessages: !isolateContext,
|
|
155
154
|
userId,
|
|
156
155
|
delegationToken,
|
|
157
156
|
agentCode,
|
|
158
157
|
taskId: state['taskId'],
|
|
159
158
|
correlationId: state['correlationId'],
|
|
160
|
-
sessionId:
|
|
161
|
-
|
|
159
|
+
sessionId: state['sessionId'],
|
|
160
|
+
checkpointNs: options?.parentNodeId,
|
|
162
161
|
skillIsolateContext: isolateContext || undefined,
|
|
163
162
|
executionContext: state['executionContext'],
|
|
164
163
|
loadedTools: scopedTools,
|
|
@@ -175,6 +174,8 @@ export class SubGraphCompiler {
|
|
|
175
174
|
lastSpanId: skillSpanId ?? state['lastSpanId'],
|
|
176
175
|
hitlChoice: state['hitlChoice'],
|
|
177
176
|
hitlRespondedBy: state['hitlRespondedBy'],
|
|
177
|
+
toolSpanId: typeof state['toolSpanId'] === 'string' ? state['toolSpanId'] : undefined,
|
|
178
|
+
hitlToolCallId: typeof state['hitlToolCallId'] === 'string' ? state['hitlToolCallId'] : undefined,
|
|
178
179
|
};
|
|
179
180
|
const result = await adapter.runIsolatedLlmStreamFlags(() => adapter.invoke(subGraph, subGraphState));
|
|
180
181
|
// 进程内调试记录;生产记录是上面的 skill Span
|
|
@@ -229,10 +230,12 @@ export class SubGraphCompiler {
|
|
|
229
230
|
const nestedLastSpanId = (typeof result.finalState?.lastSpanId === 'string' && result.finalState.lastSpanId) ||
|
|
230
231
|
skillSpanId ||
|
|
231
232
|
undefined;
|
|
233
|
+
const parentNodeId = options?.parentNodeId
|
|
234
|
+
|| (dsl.name ? `subgraph:${dsl.name}` : 'subgraph');
|
|
232
235
|
return {
|
|
233
236
|
messages: outputMessages,
|
|
234
|
-
currentNode:
|
|
235
|
-
|
|
237
|
+
currentNode: parentNodeId,
|
|
238
|
+
historyPath: [parentNodeId],
|
|
236
239
|
...(nestedLastSpanId ? { lastSpanId: nestedLastSpanId } : {}),
|
|
237
240
|
...(Array.isArray(perNode) && perNode.length > 0
|
|
238
241
|
? {
|
|
@@ -249,16 +252,10 @@ export class SubGraphCompiler {
|
|
|
249
252
|
}
|
|
250
253
|
catch (err) {
|
|
251
254
|
const hitlPaused = unwrapHitlPaused(err);
|
|
252
|
-
|
|
253
|
-
if (hitlPaused && !isolateContext) {
|
|
255
|
+
if (hitlPaused) {
|
|
254
256
|
throw err;
|
|
255
257
|
}
|
|
256
|
-
const failErr =
|
|
257
|
-
? new AppError(ErrorCodes.GRAPH_EXECUTION_ERROR, SKILL_ISOLATE_HITL_MESSAGE, {
|
|
258
|
-
exposeMessage: true,
|
|
259
|
-
cause: err,
|
|
260
|
-
})
|
|
261
|
-
: err;
|
|
258
|
+
const failErr = err;
|
|
262
259
|
const failMessage = failErr instanceof Error ? failErr.message : String(failErr);
|
|
263
260
|
await auditCtx?.finish({
|
|
264
261
|
status: 'failed',
|
|
@@ -243,6 +243,12 @@ export interface GraphState {
|
|
|
243
243
|
* 仅 stream/invoke 入口使用,不入持久化语义。
|
|
244
244
|
*/
|
|
245
245
|
replaceCheckpointMessages?: boolean;
|
|
246
|
+
/**
|
|
247
|
+
* 子图 invoke 写入 LangGraph `checkpoint_ns`。
|
|
248
|
+
* 父图为空;子图为父节点 id。存储槽与父 thread 分开,不覆盖父 latest。
|
|
249
|
+
* HITL interrupt 用同一槽;resume 返回给父图入口的 GraphState 不含本字段,也不含内层 historyPath / currentNode。
|
|
250
|
+
*/
|
|
251
|
+
checkpointNs?: string;
|
|
246
252
|
[key: string]: unknown;
|
|
247
253
|
}
|
|
248
254
|
/** Graph 内部消息格式 */
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import type { ICheckpointSaver } from '../../checkpoint/contracts/checkpoint-saver.contract.js';
|
|
14
14
|
import type { ITraceCollector } from '../../contracts/trace-collector.js';
|
|
15
15
|
import type { ISSEEmitter } from '../../sse/contracts/sse-emitter.contract.js';
|
|
16
|
-
import type { GraphState } from '../contracts/executable-graph.js';
|
|
16
|
+
import type { GraphState, CheckpointData } from '../contracts/executable-graph.js';
|
|
17
17
|
/** HITL 默认等待时长(毫秒)= 300 秒。SSE `hitl.waited.timeout` 对外用秒。 */
|
|
18
18
|
export declare const DEFAULT_HITL_TIMEOUT_MS = 300000;
|
|
19
19
|
/** 超时收口对外文案(Task / Trace / AppError);枚举走 `hitl_timeout`。 */
|
|
@@ -106,6 +106,8 @@ export declare class HitlPausedError extends Error {
|
|
|
106
106
|
export declare function unwrapHitlPaused(error: unknown): HitlPausedError | null;
|
|
107
107
|
/** Checkpoint state 中持久化的 HITL 等待元数据(进程重启 / 无内存 Map 时冷恢复) */
|
|
108
108
|
export declare const HITL_WAITING_STATE_KEY = "__hitlWaiting";
|
|
109
|
+
/** 父槽上的指针:子图 HITL 的 waiting 在 `sessionId#父节点id`,冷恢复只带 sessionId 时跟随此键。 */
|
|
110
|
+
export declare const HITL_WAITING_SLOT_KEY = "__hitlWaitingSlot";
|
|
109
111
|
export interface PersistedHitlWaiting {
|
|
110
112
|
taskId: string;
|
|
111
113
|
threadId: string;
|
|
@@ -141,6 +143,12 @@ export declare function toHitlTimeoutMs(timeoutMs?: number): number;
|
|
|
141
143
|
export declare function toHitlTimeoutSeconds(timeoutMs?: number): number;
|
|
142
144
|
export declare function isHitlDeadlinePassed(persisted: Pick<PersistedHitlWaiting, 'deadlineAt'> | undefined, now?: number): boolean;
|
|
143
145
|
export declare function readPersistedHitlWaiting(state: unknown): PersistedHitlWaiting | undefined;
|
|
146
|
+
export declare function readHitlWaitingSlot(state: unknown): string | undefined;
|
|
147
|
+
/** 父槽有 waiting 用父槽;否则跟随 `__hitlWaitingSlot` 到子图槽。 */
|
|
148
|
+
export declare function loadHitlCheckpoint(saver: ICheckpointSaver, threadId: string): Promise<{
|
|
149
|
+
threadId: string;
|
|
150
|
+
checkpoint: CheckpointData | null;
|
|
151
|
+
}>;
|
|
144
152
|
/** 门面 stream / HTTP StreamOrchestrator / streamResumeHitl 共用 */
|
|
145
153
|
export declare function buildHitlWaitedPayload(paused: HitlPausedError): {
|
|
146
154
|
question: string;
|
|
@@ -177,9 +185,10 @@ export declare class HitlHandler {
|
|
|
177
185
|
/**
|
|
178
186
|
* 中断 Graph 执行,等待人工确认
|
|
179
187
|
*
|
|
180
|
-
* 写入 `__hitlWaiting`(含 deadlineAt
|
|
181
|
-
*
|
|
182
|
-
*
|
|
188
|
+
* 写入 `__hitlWaiting`(含 deadlineAt)到当前执行图的 Checkpoint 槽。
|
|
189
|
+
* 父图槽=sessionId;子图槽=sessionId#父节点id(与 LangGraph put 同一把钥匙)。
|
|
190
|
+
* 不保留 LangGraph `engineSnapshot`:resume 从父图入口重入
|
|
191
|
+
*(skillSpanId / hitlChoice 防重复 span;子图 resume 去掉 checkpointNs / 内层 historyPath / currentNode)。
|
|
183
192
|
*/
|
|
184
193
|
interrupt(taskId: string, threadId: string, state: GraphState, config: HitlInterruptConfig, extras?: HitlInterruptExtras): Promise<void>;
|
|
185
194
|
private interruptCore;
|
|
@@ -118,6 +118,8 @@ export function unwrapHitlPaused(error) {
|
|
|
118
118
|
}
|
|
119
119
|
/** Checkpoint state 中持久化的 HITL 等待元数据(进程重启 / 无内存 Map 时冷恢复) */
|
|
120
120
|
export const HITL_WAITING_STATE_KEY = '__hitlWaiting';
|
|
121
|
+
/** 父槽上的指针:子图 HITL 的 waiting 在 `sessionId#父节点id`,冷恢复只带 sessionId 时跟随此键。 */
|
|
122
|
+
export const HITL_WAITING_SLOT_KEY = '__hitlWaitingSlot';
|
|
121
123
|
export function toHitlTimeoutMs(timeoutMs) {
|
|
122
124
|
if (typeof timeoutMs !== 'number' || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
123
125
|
return DEFAULT_HITL_TIMEOUT_MS;
|
|
@@ -142,6 +144,29 @@ export function readPersistedHitlWaiting(state) {
|
|
|
142
144
|
return undefined;
|
|
143
145
|
return raw;
|
|
144
146
|
}
|
|
147
|
+
export function readHitlWaitingSlot(state) {
|
|
148
|
+
if (!state || typeof state !== 'object')
|
|
149
|
+
return undefined;
|
|
150
|
+
const raw = state[HITL_WAITING_SLOT_KEY];
|
|
151
|
+
if (typeof raw === 'string' && raw.trim())
|
|
152
|
+
return raw.trim();
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
/** 父槽有 waiting 用父槽;否则跟随 `__hitlWaitingSlot` 到子图槽。 */
|
|
156
|
+
export async function loadHitlCheckpoint(saver, threadId) {
|
|
157
|
+
const first = await saver.load(threadId);
|
|
158
|
+
if (readPersistedHitlWaiting(first?.state)) {
|
|
159
|
+
return { threadId, checkpoint: first };
|
|
160
|
+
}
|
|
161
|
+
const slot = readHitlWaitingSlot(first?.state);
|
|
162
|
+
if (slot && slot !== threadId) {
|
|
163
|
+
const nested = await saver.load(slot);
|
|
164
|
+
if (readPersistedHitlWaiting(nested?.state)) {
|
|
165
|
+
return { threadId: slot, checkpoint: nested };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { threadId, checkpoint: first };
|
|
169
|
+
}
|
|
145
170
|
/** 门面 stream / HTTP StreamOrchestrator / streamResumeHitl 共用 */
|
|
146
171
|
export function buildHitlWaitedPayload(paused) {
|
|
147
172
|
return {
|
|
@@ -243,9 +268,10 @@ export class HitlHandler {
|
|
|
243
268
|
/**
|
|
244
269
|
* 中断 Graph 执行,等待人工确认
|
|
245
270
|
*
|
|
246
|
-
* 写入 `__hitlWaiting`(含 deadlineAt
|
|
247
|
-
*
|
|
248
|
-
*
|
|
271
|
+
* 写入 `__hitlWaiting`(含 deadlineAt)到当前执行图的 Checkpoint 槽。
|
|
272
|
+
* 父图槽=sessionId;子图槽=sessionId#父节点id(与 LangGraph put 同一把钥匙)。
|
|
273
|
+
* 不保留 LangGraph `engineSnapshot`:resume 从父图入口重入
|
|
274
|
+
*(skillSpanId / hitlChoice 防重复 span;子图 resume 去掉 checkpointNs / 内层 historyPath / currentNode)。
|
|
249
275
|
*/
|
|
250
276
|
async interrupt(taskId, threadId, state, config, extras = {}) {
|
|
251
277
|
const work = () => this.interruptCore(taskId, threadId, state, config, extras);
|
|
@@ -300,6 +326,28 @@ export class HitlHandler {
|
|
|
300
326
|
};
|
|
301
327
|
const persistedCheckpoint = stripCheckpointCollectors(checkpointData);
|
|
302
328
|
await this.checkpointSaver.save(threadId, persistedCheckpoint);
|
|
329
|
+
const parentSessionId = typeof state.sessionId === 'string' && state.sessionId.trim()
|
|
330
|
+
? state.sessionId.trim()
|
|
331
|
+
: '';
|
|
332
|
+
if (parentSessionId && threadId !== parentSessionId) {
|
|
333
|
+
const parent = await this.checkpointSaver.load(parentSessionId);
|
|
334
|
+
const pointerState = {
|
|
335
|
+
...(parent?.state ?? { messages: [], userId: state.userId ?? '', sessionId: parentSessionId }),
|
|
336
|
+
[HITL_WAITING_SLOT_KEY]: threadId,
|
|
337
|
+
};
|
|
338
|
+
await this.checkpointSaver.save(parentSessionId, stripCheckpointCollectors({
|
|
339
|
+
checkpointId: parent?.checkpointId ?? `hitl-slot-${taskId}`,
|
|
340
|
+
threadId: parentSessionId,
|
|
341
|
+
currentNodeId: parent?.currentNodeId ?? '__hitl_waiting__',
|
|
342
|
+
historyPath: parent?.historyPath ?? [],
|
|
343
|
+
state: pointerState,
|
|
344
|
+
createdBy: parent?.createdBy ?? 'system',
|
|
345
|
+
createdAt: parent?.createdAt ?? now,
|
|
346
|
+
updatedBy: 'system',
|
|
347
|
+
updatedAt: now,
|
|
348
|
+
isDeleted: false,
|
|
349
|
+
}));
|
|
350
|
+
}
|
|
303
351
|
const liveCollector = getExecutionTrace()?.collector;
|
|
304
352
|
if (liveCollector) {
|
|
305
353
|
this.waitingCollectors.set(taskId, liveCollector);
|
|
@@ -352,13 +400,14 @@ export class HitlHandler {
|
|
|
352
400
|
async resume(taskId, choice, userId, threadIdHint) {
|
|
353
401
|
return this.withTaskLock(taskId, async () => {
|
|
354
402
|
let waiting = this.waitingTasks.get(taskId);
|
|
355
|
-
const
|
|
356
|
-
if (!
|
|
403
|
+
const hinted = waiting?.threadId ?? threadIdHint;
|
|
404
|
+
if (!hinted) {
|
|
357
405
|
throw new HitlNotWaitingError(taskId);
|
|
358
406
|
}
|
|
359
|
-
const
|
|
407
|
+
const resolved = await loadHitlCheckpoint(this.checkpointSaver, hinted);
|
|
408
|
+
const checkpoint = resolved.checkpoint;
|
|
360
409
|
if (!checkpoint) {
|
|
361
|
-
throw new Error(`Checkpoint not found for thread: ${threadId}`);
|
|
410
|
+
throw new Error(`Checkpoint not found for thread: ${resolved.threadId}`);
|
|
362
411
|
}
|
|
363
412
|
const persisted = readPersistedHitlWaiting(checkpoint.state);
|
|
364
413
|
if (persisted?.timedOut) {
|
|
@@ -380,6 +429,13 @@ export class HitlHandler {
|
|
|
380
429
|
const baseState = prepareHitlCheckpointState(checkpoint.state);
|
|
381
430
|
const cleaned = { ...baseState };
|
|
382
431
|
delete cleaned[HITL_WAITING_STATE_KEY];
|
|
432
|
+
const subgraphNs = typeof cleaned['checkpointNs'] === 'string' ? String(cleaned['checkpointNs']).trim() : '';
|
|
433
|
+
delete cleaned['checkpointNs'];
|
|
434
|
+
if (subgraphNs) {
|
|
435
|
+
// 父图入口重入:内层路径不能带进父图,否则 historyPath 混入子图节点。
|
|
436
|
+
delete cleaned['historyPath'];
|
|
437
|
+
delete cleaned['currentNode'];
|
|
438
|
+
}
|
|
383
439
|
const resumedState = {
|
|
384
440
|
...cleaned,
|
|
385
441
|
hitlChoice: choice,
|
|
@@ -395,6 +451,21 @@ export class HitlHandler {
|
|
|
395
451
|
updatedBy: userId,
|
|
396
452
|
updatedAt: now,
|
|
397
453
|
}));
|
|
454
|
+
const parentSessionId = typeof resumedState.sessionId === 'string' && resumedState.sessionId.trim()
|
|
455
|
+
? resumedState.sessionId.trim()
|
|
456
|
+
: '';
|
|
457
|
+
if (parentSessionId && waiting.threadId !== parentSessionId) {
|
|
458
|
+
const parent = await this.checkpointSaver.load(parentSessionId);
|
|
459
|
+
if (readHitlWaitingSlot(parent?.state) === waiting.threadId && parent) {
|
|
460
|
+
const parentState = { ...parent.state };
|
|
461
|
+
delete parentState[HITL_WAITING_SLOT_KEY];
|
|
462
|
+
await this.checkpointSaver.save(parentSessionId, stripCheckpointCollectors({
|
|
463
|
+
...parent,
|
|
464
|
+
state: parentState,
|
|
465
|
+
updatedAt: now,
|
|
466
|
+
}));
|
|
467
|
+
}
|
|
468
|
+
}
|
|
398
469
|
const hitlSpanId = waiting.hitlSpanId ?? persisted?.hitlSpanId;
|
|
399
470
|
const resumeTraceId = resolveTraceId(resumedState) ?? resolveTraceId(checkpoint.state);
|
|
400
471
|
const resumeCtx = liveCollector && resumeTraceId
|
|
@@ -16,12 +16,12 @@ import { inferHarnessSkillDir } from '../../tool/skill-dir-infer.js';
|
|
|
16
16
|
import { isHitlDecision, unwrapHitlPaused } from '../hitl/hitl-handler.js';
|
|
17
17
|
import { ToolConfirmationInterceptor } from '../../tool/tool-confirmation.js';
|
|
18
18
|
import { resolveCheckpointThreadId } from '../../session/persist-turn-messages.js';
|
|
19
|
-
import { toolTraceIdentity } from '../../tool/mcp-list.js';
|
|
19
|
+
import { mcpToolsetCode, toolTraceIdentity } from '../../tool/mcp-list.js';
|
|
20
20
|
import { resolveExecutionTrace, warnTraceSpanFailure, } from '../execution-trace-context.js';
|
|
21
21
|
import { emitRuntimeLog } from '../../common/logger.js';
|
|
22
22
|
import { decideBeforeTool, } from '../../execution/interceptors.js';
|
|
23
23
|
import { isRetryableToolFailure, resolveToolRetryConfig, sleepToolRetry, toolRetryDelayMs, } from '../../tool/tool-retry.js';
|
|
24
|
-
import {
|
|
24
|
+
import { isIsolatedSkillState } from '../adapters/skill-isolate.js';
|
|
25
25
|
function skillDirsFromState(state) {
|
|
26
26
|
const defs = state['skillDefinitions'];
|
|
27
27
|
return Object.values(defs ?? {})
|
|
@@ -92,6 +92,7 @@ export class ToolNode {
|
|
|
92
92
|
// 4. 转换为 ToolCallRequest[](附带 manifest 路由字段)
|
|
93
93
|
const requests = lastAssistantMessage.toolCalls.map((tc) => {
|
|
94
94
|
const loaded = loadedTools?.[tc.name];
|
|
95
|
+
const toolsetCode = mcpToolsetCode(loaded);
|
|
95
96
|
const correlationId = String(state['correlationId'] ?? tc.id);
|
|
96
97
|
const taskId = String(state['taskId'] ?? state['correlationId'] ?? tc.id);
|
|
97
98
|
const command = typeof tc.arguments?.command === 'string' ? tc.arguments.command : undefined;
|
|
@@ -114,6 +115,7 @@ export class ToolNode {
|
|
|
114
115
|
// dev 上下文 DevMcpExecutor 直连所需;server 上下文会忽略
|
|
115
116
|
execution: loaded?.manifest.execution,
|
|
116
117
|
...(loaded?.manifest.version ? { toolVersion: loaded.manifest.version } : {}),
|
|
118
|
+
...(toolsetCode ? { toolsetCode } : {}),
|
|
117
119
|
correlationId,
|
|
118
120
|
taskId,
|
|
119
121
|
...(jail.workdir ? { jailWorkdir: jail.workdir } : {}),
|
|
@@ -231,10 +233,6 @@ export class ToolNode {
|
|
|
231
233
|
else {
|
|
232
234
|
const askHitl = gate.action === 'ask' || (needsHitl && confirmationTools.has(req.toolName));
|
|
233
235
|
if (askHitl && this.hitlHandler) {
|
|
234
|
-
// 隔离子图未按独立 HITL 线程设计;interrupt 前拒绝,避免写错父 Checkpoint。
|
|
235
|
-
if (!resumingHitl) {
|
|
236
|
-
assertIsolatedSkillAllowsHitl(state);
|
|
237
|
-
}
|
|
238
236
|
const confirmSet = new Set(confirmationTools);
|
|
239
237
|
if (gate.action === 'ask')
|
|
240
238
|
confirmSet.add(req.toolName);
|
|
@@ -510,10 +508,17 @@ export class ToolNode {
|
|
|
510
508
|
const delegationToken = String(executionContext?.delegationToken ??
|
|
511
509
|
state['delegationToken'] ??
|
|
512
510
|
this.defaultAuthContext.delegationToken);
|
|
511
|
+
const fromState = typeof state['agentVersion'] === 'string' ? state['agentVersion'].trim() : '';
|
|
512
|
+
const agentDir = typeof state['agentDir'] === 'string' ? state['agentDir'].trim() : '';
|
|
513
|
+
const fromDir = agentDir
|
|
514
|
+
? agentDir.split(/[/\\]/).filter(Boolean).pop()
|
|
515
|
+
: undefined;
|
|
516
|
+
const agentVersion = fromState || fromDir;
|
|
513
517
|
return {
|
|
514
518
|
userId,
|
|
515
519
|
delegationToken,
|
|
516
520
|
agentCode,
|
|
521
|
+
...(agentVersion ? { agentVersion } : {}),
|
|
517
522
|
};
|
|
518
523
|
}
|
|
519
524
|
shouldForceServer(state, toolName) {
|
|
@@ -2,6 +2,6 @@ export { EchoLLMProvider, createDefaultLLMProvider } from './echo-llm-provider.j
|
|
|
2
2
|
export { HttpLLMProvider, DEFAULT_LLM_HTTP_TIMEOUT_MS, type HttpLLMProviderConfig } from './http-llm-provider.js';
|
|
3
3
|
export { RoutingLLMProvider, type RoutingLLMProviderConfig, assertFoundationModelsInvokeAllowsStream, isFoundationModelsInvokeEnabled, MODELS_INVOKE_STREAM_MESSAGE, } from './routing-llm-provider.js';
|
|
4
4
|
export { resolveLlmModelName, canOverrideGraphModel } from './resolve-llm-model.js';
|
|
5
|
-
export { createLocalLlmProvider, requireLocalLlmConnections, resolveLocalLlmConnection, parseLocalLlmConnectionRefs, hydrateLocalLlmConnections, LLM_CONNECTIONS_ENV, } from './local-llm-provider.js';
|
|
5
|
+
export { createLocalLlmProvider, createOptionalLocalLlmProvider, requireLocalLlmConnections, resolveLocalLlmConnection, parseLocalLlmConnectionRefs, hydrateLocalLlmConnections, hasLocalLlmConnection, LLM_CONNECTIONS_ENV, } from './local-llm-provider.js';
|
|
6
6
|
export type { LocalLlmConnection, LocalLlmConnectionRef, LocalLlmConnectionRefs, LocalLlmConnections, } from './local-llm-provider.js';
|
|
7
7
|
export type { LLMToolDefinition } from '../graph/nodes/llm-node.js';
|
package/dist/providers/index.js
CHANGED
|
@@ -2,4 +2,4 @@ export { EchoLLMProvider, createDefaultLLMProvider } from './echo-llm-provider.j
|
|
|
2
2
|
export { HttpLLMProvider, DEFAULT_LLM_HTTP_TIMEOUT_MS } from './http-llm-provider.js';
|
|
3
3
|
export { RoutingLLMProvider, assertFoundationModelsInvokeAllowsStream, isFoundationModelsInvokeEnabled, MODELS_INVOKE_STREAM_MESSAGE, } from './routing-llm-provider.js';
|
|
4
4
|
export { resolveLlmModelName, canOverrideGraphModel } from './resolve-llm-model.js';
|
|
5
|
-
export { createLocalLlmProvider, requireLocalLlmConnections, resolveLocalLlmConnection, parseLocalLlmConnectionRefs, hydrateLocalLlmConnections, LLM_CONNECTIONS_ENV, } from './local-llm-provider.js';
|
|
5
|
+
export { createLocalLlmProvider, createOptionalLocalLlmProvider, requireLocalLlmConnections, resolveLocalLlmConnection, parseLocalLlmConnectionRefs, hydrateLocalLlmConnections, hasLocalLlmConnection, LLM_CONNECTIONS_ENV, } from './local-llm-provider.js';
|
|
@@ -29,6 +29,12 @@ export interface LocalLlmConnections {
|
|
|
29
29
|
export declare const LLM_CONNECTIONS_ENV = "EAP_LLM_CONNECTIONS";
|
|
30
30
|
export declare function parseLocalLlmConnectionRefs(env?: NodeJS.ProcessEnv): LocalLlmConnectionRefs;
|
|
31
31
|
export declare function hydrateLocalLlmConnections(refs: LocalLlmConnectionRefs, env?: NodeJS.ProcessEnv): LocalLlmConnections;
|
|
32
|
+
export declare function hasLocalLlmConnection(connections: LocalLlmConnections): boolean;
|
|
32
33
|
export declare function requireLocalLlmConnections(env?: NodeJS.ProcessEnv): LocalLlmConnections;
|
|
34
|
+
/**
|
|
35
|
+
* Routing 的可选 fallback:有可用本地连接才返回 Provider。
|
|
36
|
+
* 连接表 JSON 损坏仍抛错;解不出密钥则视为没有本地网关。
|
|
37
|
+
*/
|
|
38
|
+
export declare function createOptionalLocalLlmProvider(turnLog?: ILlmTurnLog, env?: NodeJS.ProcessEnv): ILLMProvider | undefined;
|
|
33
39
|
export declare function resolveLocalLlmConnection(connections: LocalLlmConnections, model?: string): LocalLlmConnection;
|
|
34
40
|
export declare function createLocalLlmProvider(connections?: LocalLlmConnections, turnLog?: ILlmTurnLog): ILLMProvider;
|
|
@@ -63,10 +63,13 @@ export function hydrateLocalLlmConnections(refs, env = process.env) {
|
|
|
63
63
|
}
|
|
64
64
|
return { default: hydrate(refs.default), byModel };
|
|
65
65
|
}
|
|
66
|
+
export function hasLocalLlmConnection(connections) {
|
|
67
|
+
return Boolean(connections.default?.apiKey) || Object.keys(connections.byModel ?? {}).length > 0;
|
|
68
|
+
}
|
|
66
69
|
export function requireLocalLlmConnections(env = process.env) {
|
|
67
70
|
const refs = parseLocalLlmConnectionRefs(env);
|
|
68
71
|
const connections = hydrateLocalLlmConnections(refs, env);
|
|
69
|
-
if (!
|
|
72
|
+
if (!hasLocalLlmConnection(connections)) {
|
|
70
73
|
const wanted = [
|
|
71
74
|
refs.default?.apiKeyEnv,
|
|
72
75
|
...Object.values(refs.byModel ?? {}).map((ref) => ref.apiKeyEnv),
|
|
@@ -75,13 +78,34 @@ export function requireLocalLlmConnections(env = process.env) {
|
|
|
75
78
|
}
|
|
76
79
|
return connections;
|
|
77
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Routing 的可选 fallback:有可用本地连接才返回 Provider。
|
|
83
|
+
* 连接表 JSON 损坏仍抛错;解不出密钥则视为没有本地网关。
|
|
84
|
+
*/
|
|
85
|
+
export function createOptionalLocalLlmProvider(turnLog, env = process.env) {
|
|
86
|
+
const refs = parseLocalLlmConnectionRefs(env);
|
|
87
|
+
const connections = hydrateLocalLlmConnections(refs, env);
|
|
88
|
+
if (!hasLocalLlmConnection(connections))
|
|
89
|
+
return undefined;
|
|
90
|
+
return createLocalLlmProvider(connections, turnLog);
|
|
91
|
+
}
|
|
92
|
+
function namedLocalModel(model) {
|
|
93
|
+
const trimmed = model?.trim();
|
|
94
|
+
if (!trimmed || trimmed.toLowerCase() === 'default')
|
|
95
|
+
return undefined;
|
|
96
|
+
return trimmed;
|
|
97
|
+
}
|
|
78
98
|
export function resolveLocalLlmConnection(connections, model) {
|
|
79
|
-
const
|
|
99
|
+
const wanted = namedLocalModel(model);
|
|
100
|
+
const named = wanted ? connections.byModel?.[wanted] : undefined;
|
|
80
101
|
const connection = named ?? connections.default;
|
|
81
102
|
if (!connection?.apiKey) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
103
|
+
const available = Object.keys(connections.byModel ?? {}).sort();
|
|
104
|
+
const availableHint = available.length > 0 ? ` Available: ${available.join(', ')}.` : '';
|
|
105
|
+
if (wanted) {
|
|
106
|
+
throw new Error(`No local LLM gateway for model ${wanted}. Set runtime.llm.models["${wanted}"] in eap.config.json.${availableHint}`);
|
|
107
|
+
}
|
|
108
|
+
throw new Error(`No local LLM gateway. Set runtime.llm.models.default or runtime.llm.baseUrl in eap.config.json.${availableHint}`);
|
|
85
109
|
}
|
|
86
110
|
return connection;
|
|
87
111
|
}
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* 3. 用返回的连接配置创建 HttpLLMProvider,发送请求
|
|
8
8
|
*
|
|
9
9
|
* 设计原则:
|
|
10
|
-
* -
|
|
10
|
+
* - 生产路径:Foundation `/models/select` 返回连接,本机 HTTP
|
|
11
|
+
* - select 失败或没有连接:走构造时注入的 fallback(dev 为 LocalLlmProvider / `resolveLocalLlmConnection`)
|
|
11
12
|
* - 支持任意 OpenAI 兼容 API(OpenAI / DeepSeek / 本地 Ollama 等)
|
|
12
|
-
* - 连接配置由 Foundation 统一管理,Runtime 无感知
|
|
13
13
|
*/
|
|
14
14
|
import type { GraphMessage } from '../graph/contracts/executable-graph.js';
|
|
15
15
|
import type { ILLMProvider, LLMNodeConfig, LLMResponse, LLMChunk } from '../graph/nodes/llm-node.js';
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* 3. 用返回的连接配置创建 HttpLLMProvider,发送请求
|
|
8
8
|
*
|
|
9
9
|
* 设计原则:
|
|
10
|
-
* -
|
|
10
|
+
* - 生产路径:Foundation `/models/select` 返回连接,本机 HTTP
|
|
11
|
+
* - select 失败或没有连接:走构造时注入的 fallback(dev 为 LocalLlmProvider / `resolveLocalLlmConnection`)
|
|
11
12
|
* - 支持任意 OpenAI 兼容 API(OpenAI / DeepSeek / 本地 Ollama 等)
|
|
12
|
-
* - 连接配置由 Foundation 统一管理,Runtime 无感知
|
|
13
13
|
*/
|
|
14
14
|
import { AppError, ErrorCodes, mergeModelCallParams } from '@gt-fe/eap-contracts';
|
|
15
15
|
import { EchoLLMProvider } from './echo-llm-provider.js';
|
|
@@ -52,7 +52,7 @@ export class RoutingLLMProvider {
|
|
|
52
52
|
return {
|
|
53
53
|
...config,
|
|
54
54
|
...merged,
|
|
55
|
-
model: selected.name,
|
|
55
|
+
model: selected.name || config.model,
|
|
56
56
|
maxContextTokens: selected.maxContextTokens ?? config.maxContextTokens,
|
|
57
57
|
};
|
|
58
58
|
}
|
|
@@ -84,7 +84,7 @@ export class RoutingLLMProvider {
|
|
|
84
84
|
catch {
|
|
85
85
|
// fallback to local
|
|
86
86
|
}
|
|
87
|
-
return { name: config.model ?? '
|
|
87
|
+
return { name: config.model ?? '', provider: 'local', connection: null };
|
|
88
88
|
}
|
|
89
89
|
useFoundationInvoke() {
|
|
90
90
|
return isFoundationModelsInvokeEnabled();
|
|
@@ -93,7 +93,6 @@ export class RoutingLLMProvider {
|
|
|
93
93
|
if (this.useFoundationInvoke()) {
|
|
94
94
|
return new FoundationModelsInvokeProvider(this.foundationUrl, selected.name, this.turnLog);
|
|
95
95
|
}
|
|
96
|
-
// 优先使用 Foundation 返回的连接配置
|
|
97
96
|
if (selected.connection) {
|
|
98
97
|
return new HttpLLMProvider({
|
|
99
98
|
apiKey: selected.connection.apiKey ?? '',
|
|
@@ -103,16 +102,7 @@ export class RoutingLLMProvider {
|
|
|
103
102
|
turnLog: this.turnLog,
|
|
104
103
|
});
|
|
105
104
|
}
|
|
106
|
-
//
|
|
107
|
-
if (process.env.OPENAI_API_KEY && selected.provider === 'openai') {
|
|
108
|
-
return new HttpLLMProvider({
|
|
109
|
-
apiKey: process.env.OPENAI_API_KEY,
|
|
110
|
-
baseUrl: process.env.OPENAI_BASE_URL,
|
|
111
|
-
defaultModel: selected.name,
|
|
112
|
-
timeoutMs: DEFAULT_LLM_HTTP_TIMEOUT_MS,
|
|
113
|
-
turnLog: this.turnLog,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
105
|
+
// Foundation 未返回连接:与 LocalLlmProvider 同一把钥匙(由调用方注入 fallback)。
|
|
116
106
|
return this.fallback;
|
|
117
107
|
}
|
|
118
108
|
}
|
|
@@ -116,7 +116,7 @@ async function findDigestMatch(items, expectedRaw) {
|
|
|
116
116
|
}
|
|
117
117
|
return undefined;
|
|
118
118
|
}
|
|
119
|
-
async function warnIfDigestMismatch(expectedRaw, actualHex, label, details) {
|
|
119
|
+
async function warnIfDigestMismatch(expectedRaw, actualHex, label, details, fail = false) {
|
|
120
120
|
const expected = normalizeDigest(expectedRaw);
|
|
121
121
|
if (!expected)
|
|
122
122
|
return;
|
|
@@ -129,6 +129,13 @@ async function warnIfDigestMismatch(expectedRaw, actualHex, label, details) {
|
|
|
129
129
|
actual,
|
|
130
130
|
...details,
|
|
131
131
|
});
|
|
132
|
+
if (fail) {
|
|
133
|
+
throw preloadPackageCorrupt(`预装校验和不匹配: ${label}`, {
|
|
134
|
+
expected,
|
|
135
|
+
actual,
|
|
136
|
+
...details,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
132
139
|
}
|
|
133
140
|
async function pickLatestVersionDir(parent) {
|
|
134
141
|
if (!(await pathExists(parent)))
|
|
@@ -373,6 +380,13 @@ export class FileSystemDeploymentSnapshot {
|
|
|
373
380
|
actual: fileDigest,
|
|
374
381
|
path: pkgFile,
|
|
375
382
|
});
|
|
383
|
+
if (this.flags.requireChecksum || isRequireChecksum()) {
|
|
384
|
+
throw preloadPackageCorrupt('snapshot.sha256 与 .package.sha256 不一致', {
|
|
385
|
+
expected: expectedPkg,
|
|
386
|
+
actual: fileDigest,
|
|
387
|
+
path: pkgFile,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
376
390
|
}
|
|
377
391
|
}
|
|
378
392
|
}
|
|
@@ -394,7 +408,7 @@ export class FileSystemDeploymentSnapshot {
|
|
|
394
408
|
throw preloadPackageCorrupt(`artifacts 指向的文件不存在: ${rel}`, { path: full });
|
|
395
409
|
}
|
|
396
410
|
const actual = await sha256OfFile(full);
|
|
397
|
-
await warnIfDigestMismatch(expected, actual, `artifact ${rel}`, { path: full });
|
|
411
|
+
await warnIfDigestMismatch(expected, actual, `artifact ${rel}`, { path: full }, this.flags.requireChecksum || isRequireChecksum());
|
|
398
412
|
}
|
|
399
413
|
}
|
|
400
414
|
async resolveAgentDir(agentCode, version) {
|
|
@@ -499,7 +513,7 @@ export class FileSystemDeploymentSnapshot {
|
|
|
499
513
|
if (ref.sha256) {
|
|
500
514
|
const matched = await findDigestMatch(existingManifests, ref.sha256);
|
|
501
515
|
if (!matched) {
|
|
502
|
-
await warnIfDigestMismatch(ref.sha256, await sha256OfFile(existingManifests[0].path), `tool ${ref.name}`, { path: existingManifests[0].path });
|
|
516
|
+
await warnIfDigestMismatch(ref.sha256, await sha256OfFile(existingManifests[0].path), `tool ${ref.name}`, { path: existingManifests[0].path }, this.flags.requireChecksum || isRequireChecksum());
|
|
503
517
|
}
|
|
504
518
|
chosen = matched ?? existingManifests[0];
|
|
505
519
|
}
|
|
@@ -576,7 +590,7 @@ export class FileSystemDeploymentSnapshot {
|
|
|
576
590
|
!isAuthorOverlay &&
|
|
577
591
|
manifestPath.endsWith('.json')) {
|
|
578
592
|
const actual = await sha256OfFile(manifestPath);
|
|
579
|
-
await warnIfDigestMismatch(ref.sha256, actual, `skill ${ref.skillId}`, { path: manifestPath });
|
|
593
|
+
await warnIfDigestMismatch(ref.sha256, actual, `skill ${ref.skillId}`, { path: manifestPath }, this.flags.requireChecksum || isRequireChecksum());
|
|
580
594
|
}
|
|
581
595
|
const skillId = String(ref.skillId);
|
|
582
596
|
return {
|
|
@@ -108,6 +108,8 @@ export declare class GraphLoader {
|
|
|
108
108
|
definitionCacheSize(): number;
|
|
109
109
|
private cloneCached;
|
|
110
110
|
loadGraph(agentCode: string, options?: LoadAgentOptions): Promise<GraphDSL>;
|
|
111
|
+
/** Agent `models.default`;无清单或未声明则 undefined。 */
|
|
112
|
+
resolveDefaultModel(agentCode: string, version?: string): Promise<string | undefined>;
|
|
111
113
|
private loadGraphFromSource;
|
|
112
114
|
/**
|
|
113
115
|
* Agent 基线系统提示词(manifest.prompts.system)。
|