@gt-fe/eap-runtime 0.0.33 → 0.0.35
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 +0 -1
- package/dist/api/handlers/invoke.handler.js +22 -5
- package/dist/api/handlers/stream.handler.d.ts +0 -1
- package/dist/api/handlers/stream.handler.js +30 -23
- package/dist/common/types.d.ts +4 -3
- package/dist/graph/adapters/langgraph.adapter.js +181 -86
- package/dist/graph/adapters/subgraph-compiler.js +25 -0
- package/dist/graph/contracts/executable-graph.d.ts +9 -0
- package/dist/graph/execution-trace-context.d.ts +3 -0
- package/dist/graph/nodes/llm-node.d.ts +7 -2
- package/dist/graph/nodes/llm-node.js +11 -4
- package/dist/graph/nodes/tool-node.d.ts +4 -0
- package/dist/graph/nodes/tool-node.js +132 -4
- package/dist/providers/http-llm-provider.js +27 -10
- package/dist/registry/graph-loader.js +10 -1
- package/dist/runtime/agent-runtime.d.ts +7 -0
- package/dist/runtime/agent-runtime.js +91 -50
- package/dist/runtime/types.d.ts +1 -1
- package/dist/session/persist-turn-messages.d.ts +10 -2
- package/dist/session/persist-turn-messages.js +35 -10
- package/dist/sse/events/index.d.ts +6 -4
- package/dist/sse/events/index.js +3 -1
- package/dist/sse/events/reasoning-events.d.ts +24 -2
- package/dist/sse/events/reasoning-events.js +3 -1
- package/dist/sse/events/skill-events.d.ts +2 -0
- package/dist/task/contracts/task-manager.contract.d.ts +2 -2
- package/dist/task/contracts/task-manager.contract.js +1 -1
- package/dist/task/index.d.ts +2 -0
- package/dist/task/index.js +1 -0
- package/dist/task/task-abort.d.ts +14 -0
- package/dist/task/task-abort.js +33 -0
- package/dist/task/task-timeout.d.ts +11 -0
- package/dist/task/task-timeout.js +40 -4
- package/dist/task/task.service.js +11 -5
- package/dist/tool/index.d.ts +2 -1
- package/dist/tool/index.js +2 -1
- package/dist/tool/mcp-tool-exposure.d.ts +113 -0
- package/dist/tool/mcp-tool-exposure.js +479 -0
- package/dist/tool/system-prompt-builder.d.ts +13 -10
- package/dist/tool/system-prompt-builder.js +18 -32
- package/package.json +1 -1
|
@@ -81,7 +81,6 @@ export declare class InvokeOrchestrator {
|
|
|
81
81
|
execute(req: InvokeRequest, userId: string): Promise<InvokeResponse>;
|
|
82
82
|
private buildResponse;
|
|
83
83
|
private buildErrorResponse;
|
|
84
|
-
private isTimeoutError;
|
|
85
84
|
private persistInvokeUsages;
|
|
86
85
|
}
|
|
87
86
|
/** @deprecated 请直接从 `graph/hitl/hitl-handler` 导入;此处保留 re-export 兼容旧测试引用 */
|
|
@@ -26,6 +26,7 @@ import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
|
|
|
26
26
|
import { classifyRuntimeError } from '../../utils/http-error.js';
|
|
27
27
|
import { DEFAULT_GRAPH_TIMEOUT_MS } from '../../common/types.js';
|
|
28
28
|
import { resolveTaskTimeoutMs } from '../../task/resolve-task-timeout.js';
|
|
29
|
+
import { taskAbortKindOf } from '../../task/task-abort.js';
|
|
29
30
|
import { runWithExecutionTrace } from '../../graph/execution-trace-context.js';
|
|
30
31
|
import { resolveAgentDisplayNameFromLoader, resolveAgentSystemPromptFromLoader, } from '../../registry/graph-loader.js';
|
|
31
32
|
import { toLoadedToolMap } from '../../tool/loaded-tool-definition.js';
|
|
@@ -313,8 +314,8 @@ export class InvokeOrchestrator {
|
|
|
313
314
|
status: 'token_exhausted',
|
|
314
315
|
});
|
|
315
316
|
}
|
|
316
|
-
|
|
317
|
-
if (
|
|
317
|
+
const abortKind = taskAbortKindOf(error, abortSignal);
|
|
318
|
+
if (abortKind === 'timeout') {
|
|
318
319
|
await this.taskService.tryUpdateStatus(task.id, 'running', 'timeout');
|
|
319
320
|
return this.buildResponse(traceId, {
|
|
320
321
|
taskId: task.id,
|
|
@@ -325,6 +326,25 @@ export class InvokeOrchestrator {
|
|
|
325
326
|
status: 'timeout',
|
|
326
327
|
});
|
|
327
328
|
}
|
|
329
|
+
if (abortKind === 'cancelled') {
|
|
330
|
+
const moved = await this.taskService.tryUpdateStatus(task.id, 'running', 'cancelled');
|
|
331
|
+
if (!moved) {
|
|
332
|
+
await this.taskService.tryUpdateStatus(task.id, 'waiting_hitl', 'cancelled');
|
|
333
|
+
}
|
|
334
|
+
// @gt-fe/eap-contracts 的 InvokeStatus 还没有 cancelled。这次不改包,响应体仍写 cancelled。
|
|
335
|
+
return {
|
|
336
|
+
code: 'runtime.task.cancelled',
|
|
337
|
+
data: {
|
|
338
|
+
taskId: task.id,
|
|
339
|
+
sessionId,
|
|
340
|
+
output: 'Task cancelled',
|
|
341
|
+
tokenUsage,
|
|
342
|
+
duration: Date.now() - startTime,
|
|
343
|
+
status: 'cancelled',
|
|
344
|
+
},
|
|
345
|
+
traceId,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
328
348
|
// ─── 错误处理:通用错误 ───
|
|
329
349
|
const classified = classifyRuntimeError(error);
|
|
330
350
|
const fuse = classified.code === ErrorCodes.GRAPH_MAX_ITERATIONS ? classified : undefined;
|
|
@@ -432,9 +452,6 @@ export class InvokeOrchestrator {
|
|
|
432
452
|
}
|
|
433
453
|
return { code: 'runtime.graph.execution_error', data, traceId };
|
|
434
454
|
}
|
|
435
|
-
isTimeoutError(error) {
|
|
436
|
-
return error instanceof Error && /timed out/i.test(error.message);
|
|
437
|
-
}
|
|
438
455
|
async persistInvokeUsages(taskId, sessionId) {
|
|
439
456
|
if (!this.checkpointSaver)
|
|
440
457
|
return [];
|
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
* @see design.md §1.5 /stream 内部执行逻辑
|
|
20
20
|
* @see requirements.md Requirement 11 AC2
|
|
21
21
|
*/
|
|
22
|
-
import { buildHitlWaitingTaskOutput, buildTurnTranscriptFromStreamEvents, loadModelFacingHistory, turnGraphMessagesAfterCompression,
|
|
22
|
+
import { buildHitlWaitingTaskOutput, buildTurnTranscriptFromStreamEvents, loadModelFacingHistory, turnGraphMessagesAfterCompression, applyStreamReasoningEvent, applyStreamTextDelta, createStreamAssistantBuckets, createStreamReasoningState, resolvedStreamReasoning, visibleStreamAssistantText, streamAssistantTexts, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, appendTerminalToolResult, } from '../../session/persist-turn-messages.js';
|
|
23
23
|
import { collectPublicArtifactLinks, collectPublicArtifactLinksFromEvent, injectPublicArtifactLinks, mergeArtifactLinks, } from '../../tool/inject-public-artifact-links.js';
|
|
24
|
+
import { correctEmployeeVisibleReply, employeeStreamClose, takeEmployeeVisibleEvents, } from '../../tool/mcp-tool-exposure.js';
|
|
24
25
|
import { SessionBudgetExhaustedError } from '../../token/session-budget-checker.js';
|
|
25
26
|
import { TokenBudgetExhaustedError } from '../../token/token-budget-guard.js';
|
|
26
27
|
import { recordTaskTokenUsages } from '../../token/record-task-token-usage.js';
|
|
@@ -42,6 +43,7 @@ import { buildHitlWaitedPayload } from '../../graph/hitl/hitl-handler.js';
|
|
|
42
43
|
import { resolveTraceId } from '../../common/trace-id.js';
|
|
43
44
|
import { DEFAULT_GRAPH_TIMEOUT_MS } from '../../common/types.js';
|
|
44
45
|
import { resolveTaskTimeoutMs } from '../../task/resolve-task-timeout.js';
|
|
46
|
+
import { taskAbortKindOf } from '../../task/task-abort.js';
|
|
45
47
|
import { runWithExecutionTrace } from '../../graph/execution-trace-context.js';
|
|
46
48
|
// ─────────────────────────────────────────────
|
|
47
49
|
// Placeholder Graph DSL (Agent Registry 接入前)
|
|
@@ -266,8 +268,8 @@ export class StreamOrchestrator {
|
|
|
266
268
|
});
|
|
267
269
|
const skillInput = resolveSkillInputFromInvokeInput(req.input);
|
|
268
270
|
const assistantBuckets = createStreamAssistantBuckets();
|
|
271
|
+
const reasoningState = createStreamReasoningState();
|
|
269
272
|
let toolCallReasoningContent;
|
|
270
|
-
let assistantReasoningContent;
|
|
271
273
|
const pendingToolCalls = [];
|
|
272
274
|
const toolResults = [];
|
|
273
275
|
const artifactLinks = [];
|
|
@@ -325,18 +327,16 @@ export class StreamOrchestrator {
|
|
|
325
327
|
arguments: p.arguments ?? {},
|
|
326
328
|
});
|
|
327
329
|
}
|
|
328
|
-
|
|
329
|
-
toolCallReasoningContent = p.reasoningContent;
|
|
330
|
-
}
|
|
330
|
+
toolCallReasoningContent = resolvedStreamReasoning(reasoningState);
|
|
331
331
|
}
|
|
332
332
|
appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
|
|
333
333
|
collectPublicArtifactLinksFromEvent(runtimeEvent.type, runtimeEvent.payload, artifactLinks);
|
|
334
|
-
|
|
335
|
-
if (streamedReasoning)
|
|
336
|
-
assistantReasoningContent = streamedReasoning;
|
|
334
|
+
applyStreamReasoningEvent(reasoningState, runtimeEvent.type, runtimeEvent.payload);
|
|
337
335
|
applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
|
|
338
|
-
|
|
339
|
-
|
|
336
|
+
for (const visibleEvent of takeEmployeeVisibleEvents(runtimeEvent.type, runtimeEvent.payload)) {
|
|
337
|
+
if (shouldEmitTokenSse(tokenDetail, visibleEvent.type)) {
|
|
338
|
+
emitter.emit(this.runtimeEventToSSE({ ...runtimeEvent, type: visibleEvent.type, payload: visibleEvent.payload }, correlationId, taskId));
|
|
339
|
+
}
|
|
340
340
|
}
|
|
341
341
|
}
|
|
342
342
|
}
|
|
@@ -369,13 +369,21 @@ export class StreamOrchestrator {
|
|
|
369
369
|
this.emitErrorAndClose(emitter, correlationId, taskId, error);
|
|
370
370
|
return;
|
|
371
371
|
}
|
|
372
|
-
|
|
373
|
-
if (
|
|
372
|
+
const abortKind = taskAbortKindOf(error, abortSignal);
|
|
373
|
+
if (abortKind === 'timeout' || abortKind === 'cancelled') {
|
|
374
374
|
await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
|
|
375
|
-
|
|
375
|
+
if (abortKind === 'timeout') {
|
|
376
|
+
await this.taskService.tryUpdateStatus(taskId, 'running', 'timeout');
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
const moved = await this.taskService.tryUpdateStatus(taskId, 'running', 'cancelled');
|
|
380
|
+
if (!moved) {
|
|
381
|
+
await this.taskService.tryUpdateStatus(taskId, 'waiting_hitl', 'cancelled');
|
|
382
|
+
}
|
|
383
|
+
}
|
|
376
384
|
emitter.emit(this.buildSSEEvent('task.error', correlationId, taskId, {
|
|
377
|
-
code: ErrorCodes.TASK_TIMEOUT,
|
|
378
|
-
message: 'Task execution timed out',
|
|
385
|
+
code: abortKind === 'timeout' ? ErrorCodes.TASK_TIMEOUT : 'runtime.task.cancelled',
|
|
386
|
+
message: abortKind === 'timeout' ? 'Task execution timed out' : 'Task cancelled',
|
|
379
387
|
recoverable: false,
|
|
380
388
|
}));
|
|
381
389
|
emitter.close();
|
|
@@ -393,12 +401,14 @@ export class StreamOrchestrator {
|
|
|
393
401
|
}
|
|
394
402
|
// ─── Step 7: 发送结束事件序列 ───
|
|
395
403
|
const tokenStats = this.tokenStatsService.getTaskStats(taskId);
|
|
396
|
-
const
|
|
404
|
+
const rawVisible = visibleStreamAssistantText(assistantBuckets);
|
|
405
|
+
const visible = correctEmployeeVisibleReply(rawVisible, toolResults);
|
|
397
406
|
const links = mergeArtifactLinks(artifactLinks, collectPublicArtifactLinks(toolResults));
|
|
398
407
|
const { injected, suffix } = injectPublicArtifactLinks(visible, links);
|
|
399
|
-
|
|
408
|
+
const close = employeeStreamClose({ raw: rawVisible, tools: toolResults, suffix });
|
|
409
|
+
for (const content of close.chunks) {
|
|
400
410
|
emitter.emit(this.buildSSEEvent('response.chunk', correlationId, taskId, {
|
|
401
|
-
content
|
|
411
|
+
content,
|
|
402
412
|
format: 'text',
|
|
403
413
|
}));
|
|
404
414
|
}
|
|
@@ -437,11 +447,11 @@ export class StreamOrchestrator {
|
|
|
437
447
|
messages: buildTurnTranscriptFromStreamEvents({
|
|
438
448
|
pendingToolCalls,
|
|
439
449
|
toolResults,
|
|
440
|
-
assistantTexts:
|
|
450
|
+
assistantTexts: close.persistCorrected ? undefined : streamAssistantTexts(assistantBuckets),
|
|
441
451
|
assistantText: injected,
|
|
442
452
|
finishPoints: graph.finishPoints,
|
|
443
453
|
toolCallReasoningContent,
|
|
444
|
-
assistantReasoningContent,
|
|
454
|
+
assistantReasoningContent: resolvedStreamReasoning(reasoningState),
|
|
445
455
|
}),
|
|
446
456
|
assistantTokenCount: tokenStats.total.output ?? 0,
|
|
447
457
|
assistantDurationMs: duration,
|
|
@@ -489,7 +499,4 @@ export class StreamOrchestrator {
|
|
|
489
499
|
}));
|
|
490
500
|
emitter.close();
|
|
491
501
|
}
|
|
492
|
-
isTimeoutError(error) {
|
|
493
|
-
return error instanceof Error && /timed out/i.test(error.message);
|
|
494
|
-
}
|
|
495
502
|
}
|
package/dist/common/types.d.ts
CHANGED
|
@@ -58,11 +58,12 @@ export interface RuntimeRunContext {
|
|
|
58
58
|
/**
|
|
59
59
|
* Task 执行状态(Runtime 内部使用)
|
|
60
60
|
*
|
|
61
|
-
* 状态流转:pending → running → success | failed | timeout | token_exhausted | waiting_hitl
|
|
61
|
+
* 状态流转:pending → running → success | failed | timeout | token_exhausted | cancelled | waiting_hitl
|
|
62
62
|
*
|
|
63
|
-
* waiting_hitl:等待人工介入确认,可通过 HITL API 恢复为 running
|
|
63
|
+
* waiting_hitl:等待人工介入确认,可通过 HITL API 恢复为 running。
|
|
64
|
+
* cancelled:调用方停止这一轮(Hosting `POST /tasks/:id/cancel`),不是超时。
|
|
64
65
|
*/
|
|
65
|
-
export type TaskStatus = 'pending' | 'running' | 'waiting_hitl' | 'success' | 'failed' | 'timeout' | 'token_exhausted';
|
|
66
|
+
export type TaskStatus = 'pending' | 'running' | 'waiting_hitl' | 'success' | 'failed' | 'timeout' | 'token_exhausted' | 'cancelled';
|
|
66
67
|
/**
|
|
67
68
|
* Runtime 执行上下文绑定状态(非平台 Session 生命周期)
|
|
68
69
|
*
|
|
@@ -23,7 +23,8 @@ import { ToolResultInjector } from '../../tool/tool-result-injector.js';
|
|
|
23
23
|
import { fileArtifactFromToolOutput } from '../../tool/file-artifact.js';
|
|
24
24
|
import { EchoLLMProvider } from '../../providers/echo-llm-provider.js';
|
|
25
25
|
import { ConditionEvaluator } from '../condition-evaluator.js';
|
|
26
|
-
import { SystemPromptBuilder, buildSkillCatalog,
|
|
26
|
+
import { SystemPromptBuilder, buildSkillCatalog, resolveLlmPromptInputs, spokenName } from '../../tool/system-prompt-builder.js';
|
|
27
|
+
import { correctEmployeeVisibleReply, lastAssistantTextSinceLastUser, exposureScopeId, exposedNamesForScope, forcedToolName, holdEmployeeTextForUnnamedMcp, resolveModelToolSurface, shouldDropUnsettledEmployeeText, toolFactsSinceLastUser, } from '../../tool/mcp-tool-exposure.js';
|
|
27
28
|
import { resolveJailFromState, resolveJailRoots } from '../../tool/harness-primitives.js';
|
|
28
29
|
import os from 'node:os';
|
|
29
30
|
import { bindSelectedSkill } from '../nodes/skill-bind-node.js';
|
|
@@ -39,6 +40,81 @@ import { recordSkillEntrySpan, skillEntryInvocationFromTool } from './skill-entr
|
|
|
39
40
|
import { llmSpanInput, llmSpanOutputFromResult, withGraphSpan } from '../trace-span.js';
|
|
40
41
|
import { executionTraceFromState, getExecutionStream, getExecutionTrace, runWithExecutionTrace, } from '../execution-trace-context.js';
|
|
41
42
|
import { isRetryableToolFailure, resolveToolRetryConfig, sleepToolRetry, toolRetryDelayMs, } from '../../tool/tool-retry.js';
|
|
43
|
+
function emitReasoningStartedIfNeeded(live, model, timestamp) {
|
|
44
|
+
if (live.reasoningStartedThisTurn)
|
|
45
|
+
return;
|
|
46
|
+
live.reasoningStartedThisTurn = true;
|
|
47
|
+
live.iteration += 1;
|
|
48
|
+
live.sink({
|
|
49
|
+
type: 'reasoning.started',
|
|
50
|
+
taskId: undefined,
|
|
51
|
+
correlationId: undefined,
|
|
52
|
+
timestamp,
|
|
53
|
+
payload: {
|
|
54
|
+
iterationCount: live.iteration,
|
|
55
|
+
model,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function nodeGraphExtra(live, nodeId) {
|
|
60
|
+
return {
|
|
61
|
+
node: nodeId,
|
|
62
|
+
...(live.graphId ? { graphId: live.graphId } : {}),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function emitLlmStreamDelta(live, nodeId, model, delta) {
|
|
66
|
+
const timestamp = new Date().toISOString();
|
|
67
|
+
const extra = nodeGraphExtra(live, nodeId);
|
|
68
|
+
if (delta.reasoningContent) {
|
|
69
|
+
emitReasoningStartedIfNeeded(live, model, timestamp);
|
|
70
|
+
live.sink({
|
|
71
|
+
type: 'reasoning.chunk',
|
|
72
|
+
taskId: undefined,
|
|
73
|
+
correlationId: undefined,
|
|
74
|
+
timestamp,
|
|
75
|
+
payload: {
|
|
76
|
+
content: delta.reasoningContent,
|
|
77
|
+
index: live.reasoningChunkIndex++,
|
|
78
|
+
...extra,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (delta.content) {
|
|
83
|
+
emitReasoningStartedIfNeeded(live, model, timestamp);
|
|
84
|
+
live.streamedTextThisTurn = true;
|
|
85
|
+
live.sink({
|
|
86
|
+
type: 'response.chunk',
|
|
87
|
+
taskId: undefined,
|
|
88
|
+
correlationId: undefined,
|
|
89
|
+
timestamp,
|
|
90
|
+
payload: {
|
|
91
|
+
content: delta.content,
|
|
92
|
+
index: live.chunkIndex++,
|
|
93
|
+
...extra,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function emitReasoningCompletedIfAny(live, nodeId, next) {
|
|
99
|
+
if (!live)
|
|
100
|
+
return;
|
|
101
|
+
const added = Array.isArray(next.messages) ? next.messages[0] : undefined;
|
|
102
|
+
const reasoning = added && typeof added === 'object' && typeof added.reasoningContent === 'string'
|
|
103
|
+
? added.reasoningContent
|
|
104
|
+
: '';
|
|
105
|
+
if (!reasoning)
|
|
106
|
+
return;
|
|
107
|
+
live.sink({
|
|
108
|
+
type: 'reasoning.completed',
|
|
109
|
+
taskId: undefined,
|
|
110
|
+
correlationId: undefined,
|
|
111
|
+
timestamp: new Date().toISOString(),
|
|
112
|
+
payload: {
|
|
113
|
+
content: reasoning,
|
|
114
|
+
...nodeGraphExtra(live, nodeId),
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
42
118
|
/**
|
|
43
119
|
* 传给 LangGraph 的 `recursionLimit`:一轮调度(superstep)+1。
|
|
44
120
|
* 同一步里并行的多个节点只 +1。ReAct 的 llm 再 tool 大约 +2。
|
|
@@ -342,7 +418,7 @@ export class LangGraphAdapter {
|
|
|
342
418
|
});
|
|
343
419
|
}
|
|
344
420
|
}
|
|
345
|
-
if (progress.status === 'started') {
|
|
421
|
+
if (progress.status === 'started' && (!progress.quiet || progress.modelOnly)) {
|
|
346
422
|
stream.sink({
|
|
347
423
|
type: 'tool.called',
|
|
348
424
|
taskId,
|
|
@@ -354,19 +430,22 @@ export class LangGraphAdapter {
|
|
|
354
430
|
...(progress.toolset ? { toolset: progress.toolset } : {}),
|
|
355
431
|
...(progress.toolVersion ? { toolVersion: progress.toolVersion } : {}),
|
|
356
432
|
arguments: progress.arguments ?? {},
|
|
433
|
+
...(progress.modelOnly ? { modelOnly: true } : {}),
|
|
357
434
|
...loc,
|
|
358
435
|
},
|
|
359
436
|
});
|
|
360
437
|
}
|
|
361
438
|
// resolvedPath / skillDirs 只给上面 classifySkillFile;inSkillSubgraph 只给 skill.started 去重。不进对话流。
|
|
362
|
-
const { resolvedPath: _resolvedPath, skillDirs: _skillDirs, inSkillSubgraph: _inSkillSubgraph, ...progressView } = progress;
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
439
|
+
const { resolvedPath: _resolvedPath, skillDirs: _skillDirs, inSkillSubgraph: _inSkillSubgraph, modelOnly: _modelOnly, ...progressView } = progress;
|
|
440
|
+
if (!progress.quiet) {
|
|
441
|
+
stream.sink({
|
|
442
|
+
type: 'tool.progress',
|
|
443
|
+
taskId,
|
|
444
|
+
correlationId,
|
|
445
|
+
timestamp,
|
|
446
|
+
payload: progressView,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
370
449
|
if (progress.status === 'completed') {
|
|
371
450
|
stream.sink({
|
|
372
451
|
type: 'tool.result',
|
|
@@ -381,6 +460,7 @@ export class LangGraphAdapter {
|
|
|
381
460
|
success: true,
|
|
382
461
|
...(progress.file ? { file: progress.file } : {}),
|
|
383
462
|
...(progress.files?.length ? { files: progress.files } : {}),
|
|
463
|
+
...(progress.modelOnly ? { modelOnly: true } : {}),
|
|
384
464
|
...loc,
|
|
385
465
|
},
|
|
386
466
|
});
|
|
@@ -397,6 +477,7 @@ export class LangGraphAdapter {
|
|
|
397
477
|
...(progress.toolset ? { toolset: progress.toolset } : {}),
|
|
398
478
|
error: progress.error ?? 'Unknown error',
|
|
399
479
|
willRetry: progress.willRetry === true,
|
|
480
|
+
...(progress.modelOnly ? { modelOnly: true } : {}),
|
|
400
481
|
...(progress.content ? { content: progress.content } : {}),
|
|
401
482
|
...(progress.file ? { file: progress.file } : {}),
|
|
402
483
|
...(progress.files?.length ? { files: progress.files } : {}),
|
|
@@ -470,6 +551,9 @@ export class LangGraphAdapter {
|
|
|
470
551
|
checkpointNs: ephemeral(undefined),
|
|
471
552
|
skillIsolateContext: ephemeral(undefined),
|
|
472
553
|
loadedTools: ephemeral(undefined),
|
|
554
|
+
exposedMcpTools: ephemeral(undefined),
|
|
555
|
+
exposedMcpScopeId: ephemeral(undefined),
|
|
556
|
+
sentMcpToolNames: ephemeral(undefined),
|
|
473
557
|
loadedSkills: ephemeral(undefined),
|
|
474
558
|
skillDefinitions: ephemeral(undefined),
|
|
475
559
|
skillInput: ephemeral(undefined),
|
|
@@ -596,15 +680,25 @@ export class LangGraphAdapter {
|
|
|
596
680
|
sink: enqueue,
|
|
597
681
|
eventContext: { taskId: state.taskId, correlationId: state.correlationId },
|
|
598
682
|
chunkIndex: 0,
|
|
683
|
+
reasoningChunkIndex: 0,
|
|
599
684
|
iteration: 0,
|
|
600
685
|
reasoningStartedThisTurn: false,
|
|
601
686
|
streamedTextThisTurn: false,
|
|
687
|
+
graphId: graph.id,
|
|
602
688
|
};
|
|
603
689
|
const traceCtx = executionTraceFromState(state);
|
|
604
690
|
const graphSignal = graphAbortSignal(state);
|
|
691
|
+
let graphIterator;
|
|
692
|
+
const stopGraphIterator = () => {
|
|
693
|
+
const iterator = graphIterator;
|
|
694
|
+
if (!iterator?.return)
|
|
695
|
+
return;
|
|
696
|
+
void iterator.return().catch(() => undefined);
|
|
697
|
+
};
|
|
605
698
|
const onGraphAbort = () => {
|
|
606
699
|
failure = graphSignal ? abortReason(graphSignal) : new Error('aborted');
|
|
607
700
|
finished = true;
|
|
701
|
+
stopGraphIterator();
|
|
608
702
|
signal();
|
|
609
703
|
};
|
|
610
704
|
graphSignal?.addEventListener('abort', onGraphAbort, { once: true });
|
|
@@ -619,9 +713,19 @@ export class LangGraphAdapter {
|
|
|
619
713
|
const streamIterator = await withGraphAbort(Promise.resolve(ref.compiled.stream(input, langGraphRunOptions({
|
|
620
714
|
configurable: this.checkpointConfigurable(state, graph),
|
|
621
715
|
streamMode: 'updates',
|
|
716
|
+
...(graphSignal ? { signal: graphSignal } : {}),
|
|
622
717
|
}))), graphSignal);
|
|
623
|
-
|
|
624
|
-
|
|
718
|
+
const iterator = streamIterator[Symbol.asyncIterator]();
|
|
719
|
+
graphIterator = iterator;
|
|
720
|
+
while (true) {
|
|
721
|
+
if (graphSignal?.aborted) {
|
|
722
|
+
failure = failure ?? abortReason(graphSignal);
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
const next = await iterator.next();
|
|
726
|
+
if (next.done)
|
|
727
|
+
break;
|
|
728
|
+
for (const event of this.convertUpdatesToEvents(next.value, graph.id, () => streamBag.chunkIndex++, ref.silentNodeIds, ref.subgraphNodeIds)) {
|
|
625
729
|
enqueue(event);
|
|
626
730
|
}
|
|
627
731
|
}
|
|
@@ -667,6 +771,7 @@ export class LangGraphAdapter {
|
|
|
667
771
|
abortDeadlineMs,
|
|
668
772
|
}, () => ref.compiled.invoke(input, langGraphRunOptions({
|
|
669
773
|
configurable: this.checkpointConfigurable(state, graph),
|
|
774
|
+
...(abortSignal ? { signal: abortSignal } : {}),
|
|
670
775
|
}))), abortSignal);
|
|
671
776
|
}
|
|
672
777
|
catch (error) {
|
|
@@ -674,10 +779,8 @@ export class LangGraphAdapter {
|
|
|
674
779
|
}
|
|
675
780
|
const duration = Date.now() - startTime;
|
|
676
781
|
const messages = Array.isArray(result['messages']) ? result['messages'] : [];
|
|
677
|
-
const
|
|
678
|
-
const output =
|
|
679
|
-
? String(lastMessage.content)
|
|
680
|
-
: '';
|
|
782
|
+
const rawOutput = lastAssistantTextSinceLastUser(messages);
|
|
783
|
+
const output = correctEmployeeVisibleReply(rawOutput, toolFactsSinceLastUser(messages));
|
|
681
784
|
// ── Token 累计:从 __llmTokenUsage 数组累加器读取每轮 LLM 明细 + 汇总
|
|
682
785
|
// 数组由 channel reducer 自动累加(每次 LLM 节点追加一条)。
|
|
683
786
|
const llmUsageArray = Array.isArray(result['__llmTokenUsage'])
|
|
@@ -702,6 +805,12 @@ export class LangGraphAdapter {
|
|
|
702
805
|
: state.messages,
|
|
703
806
|
userId: state.userId,
|
|
704
807
|
loadedTools: state.loadedTools ?? result.loadedTools,
|
|
808
|
+
exposedMcpTools: Array.isArray(result['exposedMcpTools'])
|
|
809
|
+
? result['exposedMcpTools']
|
|
810
|
+
: state.exposedMcpTools,
|
|
811
|
+
exposedMcpScopeId: typeof result['exposedMcpScopeId'] === 'string'
|
|
812
|
+
? result['exposedMcpScopeId']
|
|
813
|
+
: state.exposedMcpScopeId,
|
|
705
814
|
forceToolServer: state.forceToolServer ?? result.forceToolServer,
|
|
706
815
|
executionContext: state.executionContext ?? result.executionContext,
|
|
707
816
|
},
|
|
@@ -1082,7 +1191,6 @@ export class LangGraphAdapter {
|
|
|
1082
1191
|
console.log(`[LLM-DEBUG] node=${node.id} loadedTools=${toolNames.length}:`, toolNames);
|
|
1083
1192
|
}
|
|
1084
1193
|
const catalog = includeSkillCatalog ? buildSkillCatalog(skillDefs) : undefined;
|
|
1085
|
-
const toolCatalog = disableTools ? undefined : buildToolCatalog(loadedTools);
|
|
1086
1194
|
const jail = resolveJailFromState({
|
|
1087
1195
|
agentDir: state['agentDir'],
|
|
1088
1196
|
userId,
|
|
@@ -1107,11 +1215,18 @@ export class LangGraphAdapter {
|
|
|
1107
1215
|
agentSystemPrompt: state['agentSystemPrompt'],
|
|
1108
1216
|
nodePrompt: resolvedPrompt,
|
|
1109
1217
|
});
|
|
1218
|
+
const surface = resolveModelToolSurface({
|
|
1219
|
+
loadedTools,
|
|
1220
|
+
disableTools,
|
|
1221
|
+
exposedNames: exposedNamesForScope(state['exposedMcpTools'], state['exposedMcpScopeId'], exposureScopeId({ sessionId: state['sessionId'], taskId: state['taskId'] })),
|
|
1222
|
+
forcedName: forcedToolName(baseConfig.toolChoice),
|
|
1223
|
+
});
|
|
1224
|
+
const sentMcpToolNames = surface.apiTools.map((tool) => tool.function.name);
|
|
1110
1225
|
const assembled = this.systemPromptBuilder.build({
|
|
1111
1226
|
agentPrompt: promptInputs.agentPrompt,
|
|
1112
1227
|
nodePrompt: promptInputs.nodePrompt,
|
|
1113
1228
|
skillCatalog: catalog,
|
|
1114
|
-
|
|
1229
|
+
mcpToolIndex: surface.mcpToolIndex,
|
|
1115
1230
|
agentName,
|
|
1116
1231
|
agentCode,
|
|
1117
1232
|
userId,
|
|
@@ -1120,7 +1235,10 @@ export class LangGraphAdapter {
|
|
|
1120
1235
|
jailRoots,
|
|
1121
1236
|
skillDirs,
|
|
1122
1237
|
});
|
|
1123
|
-
const apiTools =
|
|
1238
|
+
const apiTools = surface.apiTools;
|
|
1239
|
+
const sentTools = disableTools
|
|
1240
|
+
? undefined
|
|
1241
|
+
: (apiTools.length > 0 ? apiTools : baseConfig.tools);
|
|
1124
1242
|
const finalConfig = {
|
|
1125
1243
|
...baseConfig,
|
|
1126
1244
|
systemPrompt: assembled.text,
|
|
@@ -1132,7 +1250,7 @@ export class LangGraphAdapter {
|
|
|
1132
1250
|
agentCode,
|
|
1133
1251
|
correlationId: typeof state['correlationId'] === 'string' ? state['correlationId'] : undefined,
|
|
1134
1252
|
},
|
|
1135
|
-
tools:
|
|
1253
|
+
tools: sentTools,
|
|
1136
1254
|
toolChoice: disableTools ? 'none' : baseConfig.toolChoice,
|
|
1137
1255
|
};
|
|
1138
1256
|
// 流式旁路:有本次 stream 袋时启用 LLM token 推送;silent 节点不推给用户
|
|
@@ -1141,49 +1259,30 @@ export class LangGraphAdapter {
|
|
|
1141
1259
|
if (stream) {
|
|
1142
1260
|
stream.reasoningStartedThisTurn = false;
|
|
1143
1261
|
stream.streamedTextThisTurn = false;
|
|
1262
|
+
stream.reasoningChunkIndex = 0;
|
|
1144
1263
|
}
|
|
1264
|
+
const holdEmployeeText = holdEmployeeTextForUnnamedMcp(loadedTools, sentMcpToolNames);
|
|
1265
|
+
const heldDeltas = [];
|
|
1145
1266
|
const llmNode = new LLMNode(node.id, finalConfig, this.llmProvider, enableStream, enableStream
|
|
1146
|
-
? (
|
|
1267
|
+
? (delta) => {
|
|
1147
1268
|
const live = getExecutionStream();
|
|
1148
|
-
if (!live
|
|
1269
|
+
if (!live)
|
|
1270
|
+
return;
|
|
1271
|
+
if (holdEmployeeText) {
|
|
1272
|
+
heldDeltas.push(delta);
|
|
1149
1273
|
return;
|
|
1150
|
-
const timestamp = new Date().toISOString();
|
|
1151
|
-
if (!live.reasoningStartedThisTurn) {
|
|
1152
|
-
live.reasoningStartedThisTurn = true;
|
|
1153
|
-
live.iteration += 1;
|
|
1154
|
-
live.sink({
|
|
1155
|
-
type: 'reasoning.started',
|
|
1156
|
-
taskId: undefined,
|
|
1157
|
-
correlationId: undefined,
|
|
1158
|
-
timestamp,
|
|
1159
|
-
payload: {
|
|
1160
|
-
iterationCount: live.iteration,
|
|
1161
|
-
model: finalConfig.model,
|
|
1162
|
-
},
|
|
1163
|
-
});
|
|
1164
1274
|
}
|
|
1165
|
-
live.
|
|
1166
|
-
live.sink({
|
|
1167
|
-
type: 'response.chunk',
|
|
1168
|
-
taskId: undefined,
|
|
1169
|
-
correlationId: undefined,
|
|
1170
|
-
timestamp,
|
|
1171
|
-
payload: {
|
|
1172
|
-
content: text,
|
|
1173
|
-
index: live.chunkIndex++,
|
|
1174
|
-
node: node.id,
|
|
1175
|
-
},
|
|
1176
|
-
});
|
|
1275
|
+
emitLlmStreamDelta(live, node.id, finalConfig.model, delta);
|
|
1177
1276
|
}
|
|
1178
1277
|
: undefined, this.interceptors);
|
|
1179
|
-
const toolsForSpan =
|
|
1180
|
-
?
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
? { description: t.manifest.description.trim() }
|
|
1278
|
+
const toolsForSpan = sentTools && sentTools.length > 0
|
|
1279
|
+
? sentTools.map((tool) => ({
|
|
1280
|
+
name: tool.function.name,
|
|
1281
|
+
...(tool.function.description?.trim()
|
|
1282
|
+
? { description: tool.function.description.trim() }
|
|
1185
1283
|
: {}),
|
|
1186
|
-
}))
|
|
1284
|
+
}))
|
|
1285
|
+
: undefined;
|
|
1187
1286
|
// 组装后再 createNode:input 必须是发给模型的 system/tools/messages;completeNode 不能补 input。
|
|
1188
1287
|
return withGraphSpan(state, {
|
|
1189
1288
|
name: node.id,
|
|
@@ -1197,9 +1296,31 @@ export class LangGraphAdapter {
|
|
|
1197
1296
|
},
|
|
1198
1297
|
}, async () => {
|
|
1199
1298
|
const next = await llmNode.execute(state);
|
|
1299
|
+
let dropEmployeeText = false;
|
|
1300
|
+
if (enableStream && holdEmployeeText) {
|
|
1301
|
+
const added = Array.isArray(next.messages) ? next.messages[0] : undefined;
|
|
1302
|
+
const calls = added && typeof added === 'object' && Array.isArray(added.toolCalls)
|
|
1303
|
+
? added.toolCalls
|
|
1304
|
+
: [];
|
|
1305
|
+
dropEmployeeText = shouldDropUnsettledEmployeeText(calls, loadedTools, sentMcpToolNames);
|
|
1306
|
+
const live = getExecutionStream();
|
|
1307
|
+
if (dropEmployeeText) {
|
|
1308
|
+
// 挡住 updates 再把这段正文补发成 response.chunk。
|
|
1309
|
+
if (live)
|
|
1310
|
+
live.streamedTextThisTurn = true;
|
|
1311
|
+
}
|
|
1312
|
+
else if (live) {
|
|
1313
|
+
for (const delta of heldDeltas)
|
|
1314
|
+
emitLlmStreamDelta(live, node.id, finalConfig.model, delta);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
if (enableStream && !dropEmployeeText) {
|
|
1318
|
+
emitReasoningCompletedIfAny(getExecutionStream(), node.id, next);
|
|
1319
|
+
}
|
|
1200
1320
|
const hydratedUpdate = loadedTools && loadedTools !== loadedToolsBase ? { loadedTools } : {};
|
|
1321
|
+
const sentUpdate = { sentMcpToolNames };
|
|
1201
1322
|
if (!silent)
|
|
1202
|
-
return { ...next, ...hydratedUpdate };
|
|
1323
|
+
return { ...next, ...hydratedUpdate, ...sentUpdate };
|
|
1203
1324
|
const added = Array.isArray(next.messages) ? next.messages[0] : undefined;
|
|
1204
1325
|
const raw = added && typeof added === 'object' && typeof added.content === 'string'
|
|
1205
1326
|
? added.content
|
|
@@ -1209,6 +1330,7 @@ export class LangGraphAdapter {
|
|
|
1209
1330
|
messages: [],
|
|
1210
1331
|
silentOutput: raw,
|
|
1211
1332
|
...hydratedUpdate,
|
|
1333
|
+
...sentUpdate,
|
|
1212
1334
|
};
|
|
1213
1335
|
}, { outputFrom: llmSpanOutputFromResult });
|
|
1214
1336
|
};
|
|
@@ -1542,23 +1664,17 @@ export class LangGraphAdapter {
|
|
|
1542
1664
|
continue;
|
|
1543
1665
|
}
|
|
1544
1666
|
const hasText = typeof msg.content === 'string' && msg.content.trim().length > 0;
|
|
1545
|
-
if (
|
|
1546
|
-
(!streamedTextThisTurn || msg.reasoningContent)) {
|
|
1547
|
-
if (streamedTextThisTurn && !msg.reasoningContent)
|
|
1548
|
-
continue;
|
|
1667
|
+
if (hasText && !streamedTextThisTurn) {
|
|
1549
1668
|
yield {
|
|
1550
1669
|
type: 'response.chunk',
|
|
1551
1670
|
taskId: undefined,
|
|
1552
1671
|
correlationId: undefined,
|
|
1553
1672
|
timestamp,
|
|
1554
1673
|
payload: {
|
|
1555
|
-
content:
|
|
1674
|
+
content: msg.content ?? '',
|
|
1556
1675
|
index: nextChunkIndex(),
|
|
1557
1676
|
node: nodeId,
|
|
1558
1677
|
graphId,
|
|
1559
|
-
...(msg.reasoningContent
|
|
1560
|
-
? { reasoningContent: msg.reasoningContent }
|
|
1561
|
-
: {}),
|
|
1562
1678
|
},
|
|
1563
1679
|
};
|
|
1564
1680
|
}
|
|
@@ -1569,26 +1685,8 @@ export class LangGraphAdapter {
|
|
|
1569
1685
|
continue;
|
|
1570
1686
|
if (subgraphNodeIds.has(nodeId) && streamedTextThisTurn)
|
|
1571
1687
|
continue;
|
|
1572
|
-
|
|
1573
|
-
if (streamedTextThisTurn) {
|
|
1574
|
-
// token 已流过:仍补发一条带 reasoning 的标记 chunk(空 content),供落库回传
|
|
1575
|
-
if (msg.reasoningContent) {
|
|
1576
|
-
yield {
|
|
1577
|
-
type: 'response.chunk',
|
|
1578
|
-
taskId: undefined,
|
|
1579
|
-
correlationId: undefined,
|
|
1580
|
-
timestamp,
|
|
1581
|
-
payload: {
|
|
1582
|
-
content: '',
|
|
1583
|
-
index: nextChunkIndex(),
|
|
1584
|
-
node: nodeId,
|
|
1585
|
-
graphId,
|
|
1586
|
-
reasoningContent: msg.reasoningContent,
|
|
1587
|
-
},
|
|
1588
|
-
};
|
|
1589
|
-
}
|
|
1688
|
+
if (streamedTextThisTurn)
|
|
1590
1689
|
continue;
|
|
1591
|
-
}
|
|
1592
1690
|
yield {
|
|
1593
1691
|
type: 'response.chunk',
|
|
1594
1692
|
taskId: undefined,
|
|
@@ -1599,9 +1697,6 @@ export class LangGraphAdapter {
|
|
|
1599
1697
|
index: nextChunkIndex(),
|
|
1600
1698
|
node: nodeId,
|
|
1601
1699
|
graphId,
|
|
1602
|
-
...(msg.reasoningContent
|
|
1603
|
-
? { reasoningContent: msg.reasoningContent }
|
|
1604
|
-
: {}),
|
|
1605
1700
|
},
|
|
1606
1701
|
};
|
|
1607
1702
|
continue;
|