@gt-fe/eap-runtime 0.0.33 → 0.0.34
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 +19 -17
- package/dist/common/types.d.ts +4 -3
- package/dist/graph/adapters/langgraph.adapter.js +109 -64
- 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 +8 -3
- package/dist/providers/http-llm-provider.js +27 -10
- package/dist/runtime/agent-runtime.d.ts +7 -0
- package/dist/runtime/agent-runtime.js +59 -37
- 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 +1 -1
- package/dist/tool/index.js +1 -1
- package/dist/tool/system-prompt-builder.d.ts +5 -9
- package/dist/tool/system-prompt-builder.js +13 -31
- 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,7 +19,7 @@
|
|
|
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
24
|
import { SessionBudgetExhaustedError } from '../../token/session-budget-checker.js';
|
|
25
25
|
import { TokenBudgetExhaustedError } from '../../token/token-budget-guard.js';
|
|
@@ -42,6 +42,7 @@ import { buildHitlWaitedPayload } from '../../graph/hitl/hitl-handler.js';
|
|
|
42
42
|
import { resolveTraceId } from '../../common/trace-id.js';
|
|
43
43
|
import { DEFAULT_GRAPH_TIMEOUT_MS } from '../../common/types.js';
|
|
44
44
|
import { resolveTaskTimeoutMs } from '../../task/resolve-task-timeout.js';
|
|
45
|
+
import { taskAbortKindOf } from '../../task/task-abort.js';
|
|
45
46
|
import { runWithExecutionTrace } from '../../graph/execution-trace-context.js';
|
|
46
47
|
// ─────────────────────────────────────────────
|
|
47
48
|
// Placeholder Graph DSL (Agent Registry 接入前)
|
|
@@ -266,8 +267,8 @@ export class StreamOrchestrator {
|
|
|
266
267
|
});
|
|
267
268
|
const skillInput = resolveSkillInputFromInvokeInput(req.input);
|
|
268
269
|
const assistantBuckets = createStreamAssistantBuckets();
|
|
270
|
+
const reasoningState = createStreamReasoningState();
|
|
269
271
|
let toolCallReasoningContent;
|
|
270
|
-
let assistantReasoningContent;
|
|
271
272
|
const pendingToolCalls = [];
|
|
272
273
|
const toolResults = [];
|
|
273
274
|
const artifactLinks = [];
|
|
@@ -325,15 +326,11 @@ export class StreamOrchestrator {
|
|
|
325
326
|
arguments: p.arguments ?? {},
|
|
326
327
|
});
|
|
327
328
|
}
|
|
328
|
-
|
|
329
|
-
toolCallReasoningContent = p.reasoningContent;
|
|
330
|
-
}
|
|
329
|
+
toolCallReasoningContent = resolvedStreamReasoning(reasoningState);
|
|
331
330
|
}
|
|
332
331
|
appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
|
|
333
332
|
collectPublicArtifactLinksFromEvent(runtimeEvent.type, runtimeEvent.payload, artifactLinks);
|
|
334
|
-
|
|
335
|
-
if (streamedReasoning)
|
|
336
|
-
assistantReasoningContent = streamedReasoning;
|
|
333
|
+
applyStreamReasoningEvent(reasoningState, runtimeEvent.type, runtimeEvent.payload);
|
|
337
334
|
applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
|
|
338
335
|
if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
|
|
339
336
|
emitter.emit(this.runtimeEventToSSE(runtimeEvent, correlationId, taskId));
|
|
@@ -369,13 +366,21 @@ export class StreamOrchestrator {
|
|
|
369
366
|
this.emitErrorAndClose(emitter, correlationId, taskId, error);
|
|
370
367
|
return;
|
|
371
368
|
}
|
|
372
|
-
|
|
373
|
-
if (
|
|
369
|
+
const abortKind = taskAbortKindOf(error, abortSignal);
|
|
370
|
+
if (abortKind === 'timeout' || abortKind === 'cancelled') {
|
|
374
371
|
await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
|
|
375
|
-
|
|
372
|
+
if (abortKind === 'timeout') {
|
|
373
|
+
await this.taskService.tryUpdateStatus(taskId, 'running', 'timeout');
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
const moved = await this.taskService.tryUpdateStatus(taskId, 'running', 'cancelled');
|
|
377
|
+
if (!moved) {
|
|
378
|
+
await this.taskService.tryUpdateStatus(taskId, 'waiting_hitl', 'cancelled');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
376
381
|
emitter.emit(this.buildSSEEvent('task.error', correlationId, taskId, {
|
|
377
|
-
code: ErrorCodes.TASK_TIMEOUT,
|
|
378
|
-
message: 'Task execution timed out',
|
|
382
|
+
code: abortKind === 'timeout' ? ErrorCodes.TASK_TIMEOUT : 'runtime.task.cancelled',
|
|
383
|
+
message: abortKind === 'timeout' ? 'Task execution timed out' : 'Task cancelled',
|
|
379
384
|
recoverable: false,
|
|
380
385
|
}));
|
|
381
386
|
emitter.close();
|
|
@@ -441,7 +446,7 @@ export class StreamOrchestrator {
|
|
|
441
446
|
assistantText: injected,
|
|
442
447
|
finishPoints: graph.finishPoints,
|
|
443
448
|
toolCallReasoningContent,
|
|
444
|
-
assistantReasoningContent,
|
|
449
|
+
assistantReasoningContent: resolvedStreamReasoning(reasoningState),
|
|
445
450
|
}),
|
|
446
451
|
assistantTokenCount: tokenStats.total.output ?? 0,
|
|
447
452
|
assistantDurationMs: duration,
|
|
@@ -489,7 +494,4 @@ export class StreamOrchestrator {
|
|
|
489
494
|
}));
|
|
490
495
|
emitter.close();
|
|
491
496
|
}
|
|
492
|
-
isTimeoutError(error) {
|
|
493
|
-
return error instanceof Error && /timed out/i.test(error.message);
|
|
494
|
-
}
|
|
495
497
|
}
|
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,7 @@ 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, loadedToolsToApiTools, resolveLlmPromptInputs, spokenName } from '../../tool/system-prompt-builder.js';
|
|
27
27
|
import { resolveJailFromState, resolveJailRoots } from '../../tool/harness-primitives.js';
|
|
28
28
|
import os from 'node:os';
|
|
29
29
|
import { bindSelectedSkill } from '../nodes/skill-bind-node.js';
|
|
@@ -39,6 +39,81 @@ import { recordSkillEntrySpan, skillEntryInvocationFromTool } from './skill-entr
|
|
|
39
39
|
import { llmSpanInput, llmSpanOutputFromResult, withGraphSpan } from '../trace-span.js';
|
|
40
40
|
import { executionTraceFromState, getExecutionStream, getExecutionTrace, runWithExecutionTrace, } from '../execution-trace-context.js';
|
|
41
41
|
import { isRetryableToolFailure, resolveToolRetryConfig, sleepToolRetry, toolRetryDelayMs, } from '../../tool/tool-retry.js';
|
|
42
|
+
function emitReasoningStartedIfNeeded(live, model, timestamp) {
|
|
43
|
+
if (live.reasoningStartedThisTurn)
|
|
44
|
+
return;
|
|
45
|
+
live.reasoningStartedThisTurn = true;
|
|
46
|
+
live.iteration += 1;
|
|
47
|
+
live.sink({
|
|
48
|
+
type: 'reasoning.started',
|
|
49
|
+
taskId: undefined,
|
|
50
|
+
correlationId: undefined,
|
|
51
|
+
timestamp,
|
|
52
|
+
payload: {
|
|
53
|
+
iterationCount: live.iteration,
|
|
54
|
+
model,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function nodeGraphExtra(live, nodeId) {
|
|
59
|
+
return {
|
|
60
|
+
node: nodeId,
|
|
61
|
+
...(live.graphId ? { graphId: live.graphId } : {}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function emitLlmStreamDelta(live, nodeId, model, delta) {
|
|
65
|
+
const timestamp = new Date().toISOString();
|
|
66
|
+
const extra = nodeGraphExtra(live, nodeId);
|
|
67
|
+
if (delta.reasoningContent) {
|
|
68
|
+
emitReasoningStartedIfNeeded(live, model, timestamp);
|
|
69
|
+
live.sink({
|
|
70
|
+
type: 'reasoning.chunk',
|
|
71
|
+
taskId: undefined,
|
|
72
|
+
correlationId: undefined,
|
|
73
|
+
timestamp,
|
|
74
|
+
payload: {
|
|
75
|
+
content: delta.reasoningContent,
|
|
76
|
+
index: live.reasoningChunkIndex++,
|
|
77
|
+
...extra,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (delta.content) {
|
|
82
|
+
emitReasoningStartedIfNeeded(live, model, timestamp);
|
|
83
|
+
live.streamedTextThisTurn = true;
|
|
84
|
+
live.sink({
|
|
85
|
+
type: 'response.chunk',
|
|
86
|
+
taskId: undefined,
|
|
87
|
+
correlationId: undefined,
|
|
88
|
+
timestamp,
|
|
89
|
+
payload: {
|
|
90
|
+
content: delta.content,
|
|
91
|
+
index: live.chunkIndex++,
|
|
92
|
+
...extra,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function emitReasoningCompletedIfAny(live, nodeId, next) {
|
|
98
|
+
if (!live)
|
|
99
|
+
return;
|
|
100
|
+
const added = Array.isArray(next.messages) ? next.messages[0] : undefined;
|
|
101
|
+
const reasoning = added && typeof added === 'object' && typeof added.reasoningContent === 'string'
|
|
102
|
+
? added.reasoningContent
|
|
103
|
+
: '';
|
|
104
|
+
if (!reasoning)
|
|
105
|
+
return;
|
|
106
|
+
live.sink({
|
|
107
|
+
type: 'reasoning.completed',
|
|
108
|
+
taskId: undefined,
|
|
109
|
+
correlationId: undefined,
|
|
110
|
+
timestamp: new Date().toISOString(),
|
|
111
|
+
payload: {
|
|
112
|
+
content: reasoning,
|
|
113
|
+
...nodeGraphExtra(live, nodeId),
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
42
117
|
/**
|
|
43
118
|
* 传给 LangGraph 的 `recursionLimit`:一轮调度(superstep)+1。
|
|
44
119
|
* 同一步里并行的多个节点只 +1。ReAct 的 llm 再 tool 大约 +2。
|
|
@@ -596,15 +671,25 @@ export class LangGraphAdapter {
|
|
|
596
671
|
sink: enqueue,
|
|
597
672
|
eventContext: { taskId: state.taskId, correlationId: state.correlationId },
|
|
598
673
|
chunkIndex: 0,
|
|
674
|
+
reasoningChunkIndex: 0,
|
|
599
675
|
iteration: 0,
|
|
600
676
|
reasoningStartedThisTurn: false,
|
|
601
677
|
streamedTextThisTurn: false,
|
|
678
|
+
graphId: graph.id,
|
|
602
679
|
};
|
|
603
680
|
const traceCtx = executionTraceFromState(state);
|
|
604
681
|
const graphSignal = graphAbortSignal(state);
|
|
682
|
+
let graphIterator;
|
|
683
|
+
const stopGraphIterator = () => {
|
|
684
|
+
const iterator = graphIterator;
|
|
685
|
+
if (!iterator?.return)
|
|
686
|
+
return;
|
|
687
|
+
void iterator.return().catch(() => undefined);
|
|
688
|
+
};
|
|
605
689
|
const onGraphAbort = () => {
|
|
606
690
|
failure = graphSignal ? abortReason(graphSignal) : new Error('aborted');
|
|
607
691
|
finished = true;
|
|
692
|
+
stopGraphIterator();
|
|
608
693
|
signal();
|
|
609
694
|
};
|
|
610
695
|
graphSignal?.addEventListener('abort', onGraphAbort, { once: true });
|
|
@@ -619,9 +704,19 @@ export class LangGraphAdapter {
|
|
|
619
704
|
const streamIterator = await withGraphAbort(Promise.resolve(ref.compiled.stream(input, langGraphRunOptions({
|
|
620
705
|
configurable: this.checkpointConfigurable(state, graph),
|
|
621
706
|
streamMode: 'updates',
|
|
707
|
+
...(graphSignal ? { signal: graphSignal } : {}),
|
|
622
708
|
}))), graphSignal);
|
|
623
|
-
|
|
624
|
-
|
|
709
|
+
const iterator = streamIterator[Symbol.asyncIterator]();
|
|
710
|
+
graphIterator = iterator;
|
|
711
|
+
while (true) {
|
|
712
|
+
if (graphSignal?.aborted) {
|
|
713
|
+
failure = failure ?? abortReason(graphSignal);
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
const next = await iterator.next();
|
|
717
|
+
if (next.done)
|
|
718
|
+
break;
|
|
719
|
+
for (const event of this.convertUpdatesToEvents(next.value, graph.id, () => streamBag.chunkIndex++, ref.silentNodeIds, ref.subgraphNodeIds)) {
|
|
625
720
|
enqueue(event);
|
|
626
721
|
}
|
|
627
722
|
}
|
|
@@ -667,6 +762,7 @@ export class LangGraphAdapter {
|
|
|
667
762
|
abortDeadlineMs,
|
|
668
763
|
}, () => ref.compiled.invoke(input, langGraphRunOptions({
|
|
669
764
|
configurable: this.checkpointConfigurable(state, graph),
|
|
765
|
+
...(abortSignal ? { signal: abortSignal } : {}),
|
|
670
766
|
}))), abortSignal);
|
|
671
767
|
}
|
|
672
768
|
catch (error) {
|
|
@@ -1082,7 +1178,6 @@ export class LangGraphAdapter {
|
|
|
1082
1178
|
console.log(`[LLM-DEBUG] node=${node.id} loadedTools=${toolNames.length}:`, toolNames);
|
|
1083
1179
|
}
|
|
1084
1180
|
const catalog = includeSkillCatalog ? buildSkillCatalog(skillDefs) : undefined;
|
|
1085
|
-
const toolCatalog = disableTools ? undefined : buildToolCatalog(loadedTools);
|
|
1086
1181
|
const jail = resolveJailFromState({
|
|
1087
1182
|
agentDir: state['agentDir'],
|
|
1088
1183
|
userId,
|
|
@@ -1111,7 +1206,6 @@ export class LangGraphAdapter {
|
|
|
1111
1206
|
agentPrompt: promptInputs.agentPrompt,
|
|
1112
1207
|
nodePrompt: promptInputs.nodePrompt,
|
|
1113
1208
|
skillCatalog: catalog,
|
|
1114
|
-
toolCatalog,
|
|
1115
1209
|
agentName,
|
|
1116
1210
|
agentCode,
|
|
1117
1211
|
userId,
|
|
@@ -1141,39 +1235,14 @@ export class LangGraphAdapter {
|
|
|
1141
1235
|
if (stream) {
|
|
1142
1236
|
stream.reasoningStartedThisTurn = false;
|
|
1143
1237
|
stream.streamedTextThisTurn = false;
|
|
1238
|
+
stream.reasoningChunkIndex = 0;
|
|
1144
1239
|
}
|
|
1145
1240
|
const llmNode = new LLMNode(node.id, finalConfig, this.llmProvider, enableStream, enableStream
|
|
1146
|
-
? (
|
|
1241
|
+
? (delta) => {
|
|
1147
1242
|
const live = getExecutionStream();
|
|
1148
|
-
if (!live
|
|
1243
|
+
if (!live)
|
|
1149
1244
|
return;
|
|
1150
|
-
|
|
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
|
-
}
|
|
1165
|
-
live.streamedTextThisTurn = true;
|
|
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
|
-
});
|
|
1245
|
+
emitLlmStreamDelta(live, node.id, finalConfig.model, delta);
|
|
1177
1246
|
}
|
|
1178
1247
|
: undefined, this.interceptors);
|
|
1179
1248
|
const toolsForSpan = disableTools || !loadedTools
|
|
@@ -1197,6 +1266,9 @@ export class LangGraphAdapter {
|
|
|
1197
1266
|
},
|
|
1198
1267
|
}, async () => {
|
|
1199
1268
|
const next = await llmNode.execute(state);
|
|
1269
|
+
if (enableStream) {
|
|
1270
|
+
emitReasoningCompletedIfAny(getExecutionStream(), node.id, next);
|
|
1271
|
+
}
|
|
1200
1272
|
const hydratedUpdate = loadedTools && loadedTools !== loadedToolsBase ? { loadedTools } : {};
|
|
1201
1273
|
if (!silent)
|
|
1202
1274
|
return { ...next, ...hydratedUpdate };
|
|
@@ -1542,23 +1614,17 @@ export class LangGraphAdapter {
|
|
|
1542
1614
|
continue;
|
|
1543
1615
|
}
|
|
1544
1616
|
const hasText = typeof msg.content === 'string' && msg.content.trim().length > 0;
|
|
1545
|
-
if (
|
|
1546
|
-
(!streamedTextThisTurn || msg.reasoningContent)) {
|
|
1547
|
-
if (streamedTextThisTurn && !msg.reasoningContent)
|
|
1548
|
-
continue;
|
|
1617
|
+
if (hasText && !streamedTextThisTurn) {
|
|
1549
1618
|
yield {
|
|
1550
1619
|
type: 'response.chunk',
|
|
1551
1620
|
taskId: undefined,
|
|
1552
1621
|
correlationId: undefined,
|
|
1553
1622
|
timestamp,
|
|
1554
1623
|
payload: {
|
|
1555
|
-
content:
|
|
1624
|
+
content: msg.content ?? '',
|
|
1556
1625
|
index: nextChunkIndex(),
|
|
1557
1626
|
node: nodeId,
|
|
1558
1627
|
graphId,
|
|
1559
|
-
...(msg.reasoningContent
|
|
1560
|
-
? { reasoningContent: msg.reasoningContent }
|
|
1561
|
-
: {}),
|
|
1562
1628
|
},
|
|
1563
1629
|
};
|
|
1564
1630
|
}
|
|
@@ -1569,26 +1635,8 @@ export class LangGraphAdapter {
|
|
|
1569
1635
|
continue;
|
|
1570
1636
|
if (subgraphNodeIds.has(nodeId) && streamedTextThisTurn)
|
|
1571
1637
|
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
|
-
}
|
|
1638
|
+
if (streamedTextThisTurn)
|
|
1590
1639
|
continue;
|
|
1591
|
-
}
|
|
1592
1640
|
yield {
|
|
1593
1641
|
type: 'response.chunk',
|
|
1594
1642
|
taskId: undefined,
|
|
@@ -1599,9 +1647,6 @@ export class LangGraphAdapter {
|
|
|
1599
1647
|
index: nextChunkIndex(),
|
|
1600
1648
|
node: nodeId,
|
|
1601
1649
|
graphId,
|
|
1602
|
-
...(msg.reasoningContent
|
|
1603
|
-
? { reasoningContent: msg.reasoningContent }
|
|
1604
|
-
: {}),
|
|
1605
1650
|
},
|
|
1606
1651
|
};
|
|
1607
1652
|
continue;
|
|
@@ -8,9 +8,12 @@ export interface ExecutionStreamBag {
|
|
|
8
8
|
correlationId?: string;
|
|
9
9
|
};
|
|
10
10
|
chunkIndex: number;
|
|
11
|
+
/** 本轮 reasoning.chunk 序号,与 chunkIndex 分开从 0 计 */
|
|
12
|
+
reasoningChunkIndex: number;
|
|
11
13
|
iteration: number;
|
|
12
14
|
reasoningStartedThisTurn: boolean;
|
|
13
15
|
streamedTextThisTurn: boolean;
|
|
16
|
+
graphId?: string;
|
|
14
17
|
}
|
|
15
18
|
export interface ExecutionRunContext {
|
|
16
19
|
collector?: ITraceCollector;
|
|
@@ -107,6 +107,11 @@ export interface LLMChunk {
|
|
|
107
107
|
/** thinking 增量;最终聚合后写入 assistant 消息 */
|
|
108
108
|
reasoningContent?: string;
|
|
109
109
|
}
|
|
110
|
+
/** 流式旁路增量:思考与正文分字段,空字段不传 */
|
|
111
|
+
export type LLMStreamDelta = {
|
|
112
|
+
content?: string;
|
|
113
|
+
reasoningContent?: string;
|
|
114
|
+
};
|
|
110
115
|
/**
|
|
111
116
|
* ILLMProvider — LLM 调用提供者接口
|
|
112
117
|
*
|
|
@@ -145,9 +150,9 @@ export declare class LLMNode {
|
|
|
145
150
|
* @param config - LLM 节点配置
|
|
146
151
|
* @param provider - LLM 调用提供者
|
|
147
152
|
* @param streaming - 是否启用流式输出(默认 false)
|
|
148
|
-
* @param onChunk -
|
|
153
|
+
* @param onChunk - 流式旁路:思考走 reasoningContent,正文走 content
|
|
149
154
|
*/
|
|
150
|
-
constructor(nodeId: string, config: LLMNodeConfig, provider: ILLMProvider, streaming?: boolean, onChunk?: (
|
|
155
|
+
constructor(nodeId: string, config: LLMNodeConfig, provider: ILLMProvider, streaming?: boolean, onChunk?: (delta: LLMStreamDelta) => void, interceptors?: Pick<ExecutionInterceptors, "beforeModel" | "afterModel"> | undefined);
|
|
151
156
|
/**
|
|
152
157
|
* execute — StateGraph 节点函数
|
|
153
158
|
*
|
|
@@ -52,7 +52,7 @@ export class LLMNode {
|
|
|
52
52
|
* @param config - LLM 节点配置
|
|
53
53
|
* @param provider - LLM 调用提供者
|
|
54
54
|
* @param streaming - 是否启用流式输出(默认 false)
|
|
55
|
-
* @param onChunk -
|
|
55
|
+
* @param onChunk - 流式旁路:思考走 reasoningContent,正文走 content
|
|
56
56
|
*/
|
|
57
57
|
constructor(nodeId, config, provider, streaming = false, onChunk, interceptors) {
|
|
58
58
|
this.interceptors = interceptors;
|
|
@@ -247,8 +247,13 @@ export class LLMNode {
|
|
|
247
247
|
let toolCalls;
|
|
248
248
|
const iterator = this.provider.stream(messages, this.config);
|
|
249
249
|
for await (const chunk of iterator) {
|
|
250
|
-
|
|
251
|
-
|
|
250
|
+
const content = chunk.content || '';
|
|
251
|
+
const reasoningContent = chunk.reasoningContent || '';
|
|
252
|
+
if (this.onChunk && (content || reasoningContent)) {
|
|
253
|
+
this.onChunk({
|
|
254
|
+
...(content ? { content } : {}),
|
|
255
|
+
...(reasoningContent ? { reasoningContent } : {}),
|
|
256
|
+
});
|
|
252
257
|
}
|
|
253
258
|
chunks.push(chunk.content);
|
|
254
259
|
if (chunk.reasoningContent) {
|
|
@@ -119,7 +119,7 @@ export class HttpLLMProvider {
|
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
121
|
catch (err) {
|
|
122
|
-
|
|
122
|
+
throwIfGraphAborted();
|
|
123
123
|
throw new Error(this.formatNetworkError(url, err));
|
|
124
124
|
}
|
|
125
125
|
if (!response.ok) {
|
|
@@ -170,6 +170,7 @@ export class HttpLLMProvider {
|
|
|
170
170
|
return result;
|
|
171
171
|
}
|
|
172
172
|
catch (err) {
|
|
173
|
+
const propagated = graphAbortOr(err);
|
|
173
174
|
await this.writeTurn({
|
|
174
175
|
url,
|
|
175
176
|
messages,
|
|
@@ -180,10 +181,10 @@ export class HttpLLMProvider {
|
|
|
180
181
|
inputTokens: 0,
|
|
181
182
|
outputTokens: 0,
|
|
182
183
|
finishReason: 'error',
|
|
183
|
-
error:
|
|
184
|
+
error: propagated instanceof Error ? propagated.message : String(propagated),
|
|
184
185
|
},
|
|
185
186
|
});
|
|
186
|
-
throw
|
|
187
|
+
throw propagated;
|
|
187
188
|
}
|
|
188
189
|
finally {
|
|
189
190
|
clearTimeout(timeout);
|
|
@@ -244,7 +245,7 @@ export class HttpLLMProvider {
|
|
|
244
245
|
});
|
|
245
246
|
}
|
|
246
247
|
catch (err) {
|
|
247
|
-
|
|
248
|
+
throwIfGraphAborted();
|
|
248
249
|
throw new Error(this.formatNetworkError(url, err));
|
|
249
250
|
}
|
|
250
251
|
if (!response.ok) {
|
|
@@ -397,6 +398,7 @@ export class HttpLLMProvider {
|
|
|
397
398
|
});
|
|
398
399
|
}
|
|
399
400
|
catch (err) {
|
|
401
|
+
const propagated = graphAbortOr(err);
|
|
400
402
|
await this.writeTurn({
|
|
401
403
|
url,
|
|
402
404
|
messages,
|
|
@@ -411,10 +413,10 @@ export class HttpLLMProvider {
|
|
|
411
413
|
inputTokens,
|
|
412
414
|
outputTokens,
|
|
413
415
|
finishReason: 'error',
|
|
414
|
-
error:
|
|
416
|
+
error: propagated instanceof Error ? propagated.message : String(propagated),
|
|
415
417
|
},
|
|
416
418
|
});
|
|
417
|
-
throw
|
|
419
|
+
throw propagated;
|
|
418
420
|
}
|
|
419
421
|
finally {
|
|
420
422
|
clearTimeout(timeout);
|
|
@@ -528,11 +530,26 @@ function linkGraphAbort(controller) {
|
|
|
528
530
|
graphSignal.addEventListener('abort', onAbort, { once: true });
|
|
529
531
|
return () => graphSignal.removeEventListener('abort', onAbort);
|
|
530
532
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
533
|
+
/** 这一轮 signal 已 abort 就抛出 reason。超时和用户停止都走这里,不再只认 timed out。 */
|
|
534
|
+
function throwIfGraphAborted() {
|
|
535
|
+
const reason = graphAbortReason();
|
|
536
|
+
if (reason)
|
|
534
537
|
throw reason;
|
|
535
|
-
|
|
538
|
+
}
|
|
539
|
+
/** 流式 read 抛出的 DOMException 换成这一轮 signal 上的 reason。 */
|
|
540
|
+
function graphAbortOr(err) {
|
|
541
|
+
return graphAbortReason() ?? err;
|
|
542
|
+
}
|
|
543
|
+
function graphAbortReason() {
|
|
544
|
+
const signal = getExecutionTrace()?.abortSignal;
|
|
545
|
+
if (!signal?.aborted)
|
|
546
|
+
return undefined;
|
|
547
|
+
const reason = signal.reason;
|
|
548
|
+
if (reason instanceof Error)
|
|
549
|
+
return reason;
|
|
550
|
+
const err = new Error(reason == null ? 'Task aborted' : String(reason));
|
|
551
|
+
err.name = 'AbortError';
|
|
552
|
+
return err;
|
|
536
553
|
}
|
|
537
554
|
function formatNetworkDetail(error, depth = 0) {
|
|
538
555
|
if (depth > 5)
|
|
@@ -11,6 +11,7 @@ import type { ICheckpointSaver } from '../checkpoint/contracts/checkpoint-saver.
|
|
|
11
11
|
import type { ITraceCollector } from '../contracts/trace-collector.js';
|
|
12
12
|
import type { IStateGraphEngine } from '../graph/contracts/state-graph.contract.js';
|
|
13
13
|
import type { TaskTimeoutManager } from '../task/task-timeout.js';
|
|
14
|
+
import { type TaskAbortKind } from '../task/task-abort.js';
|
|
14
15
|
import { HitlHandler } from '../graph/hitl/hitl-handler.js';
|
|
15
16
|
import { type GraphLoader } from '../registry/graph-loader.js';
|
|
16
17
|
import type { SessionContextService } from '../session/session-context.service.js';
|
|
@@ -88,6 +89,12 @@ export interface ConversationDetail {
|
|
|
88
89
|
export declare class AgentRuntime {
|
|
89
90
|
private readonly deps;
|
|
90
91
|
constructor(deps: AgentRuntimeDeps);
|
|
92
|
+
/**
|
|
93
|
+
* 停止这一轮。超时和用户停止共用 timeoutManager 上的那条 AbortSignal。
|
|
94
|
+
* 计时器还没启动时先记下,startTimer 会返回已 abort 的 signal,且不调用 onTimeout。
|
|
95
|
+
* @returns 已 abort 或已记下。没有 timeoutManager 时返回 false。
|
|
96
|
+
*/
|
|
97
|
+
abort(taskId: string, kind?: TaskAbortKind): boolean;
|
|
91
98
|
/** 从 GraphLoader 解析 tool manifest 映射(invoke/stream/resume/fork 共用) */
|
|
92
99
|
private resolveLoadedTools;
|
|
93
100
|
/** 从预装重载 Skill 定义(HITL/Fork 时 ephemeral 字段可能丢失) */
|