@gt-fe/eap-runtime 0.0.20 → 0.0.22
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 +4 -3
- package/dist/api/handlers/invoke.handler.d.ts +7 -0
- package/dist/api/handlers/invoke.handler.js +52 -10
- package/dist/api/handlers/stream.handler.d.ts +6 -0
- package/dist/api/handlers/stream.handler.js +60 -39
- package/dist/api/routes/agent.routes.d.ts +8 -1
- 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/common/types.d.ts +2 -2
- package/dist/common/types.js +1 -1
- package/dist/execution/index.d.ts +2 -0
- package/dist/execution/index.js +2 -0
- package/dist/execution/interceptors.d.ts +40 -0
- package/dist/execution/interceptors.js +23 -0
- package/dist/execution/ports.d.ts +18 -0
- package/dist/execution/ports.js +13 -0
- package/dist/governance/audit-reporter.d.ts +14 -0
- package/dist/governance/audit-reporter.js +18 -0
- package/dist/governance/index.d.ts +2 -0
- package/dist/governance/index.js +1 -0
- package/dist/governance/permission-guard.d.ts +6 -2
- package/dist/governance/permission-guard.js +38 -5
- package/dist/graph/adapters/graph-nesting.d.ts +12 -0
- package/dist/graph/adapters/graph-nesting.js +29 -0
- package/dist/graph/adapters/index.d.ts +2 -0
- package/dist/graph/adapters/index.js +2 -0
- 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 +33 -11
- package/dist/graph/adapters/langgraph.adapter.js +200 -98
- package/dist/graph/adapters/skill-isolate.d.ts +13 -0
- package/dist/graph/adapters/skill-isolate.js +39 -0
- package/dist/graph/adapters/subgraph-compiler.d.ts +7 -2
- package/dist/graph/adapters/subgraph-compiler.js +63 -30
- package/dist/graph/contracts/executable-graph.d.ts +33 -5
- package/dist/graph/hitl/hitl-handler.d.ts +13 -4
- package/dist/graph/hitl/hitl-handler.js +80 -7
- package/dist/graph/nodes/index.d.ts +1 -0
- package/dist/graph/nodes/index.js +1 -0
- package/dist/graph/nodes/llm-node.d.ts +3 -1
- package/dist/graph/nodes/llm-node.js +26 -2
- package/dist/graph/nodes/rag/dedup.d.ts +20 -0
- package/dist/graph/nodes/rag/dedup.js +77 -0
- package/dist/graph/nodes/rag/denoise.d.ts +58 -0
- package/dist/graph/nodes/rag/denoise.js +220 -0
- package/dist/graph/nodes/rag/fusion/fuse.d.ts +11 -0
- package/dist/graph/nodes/rag/fusion/fuse.js +25 -0
- package/dist/graph/nodes/rag/fusion/normalize.d.ts +9 -0
- package/dist/graph/nodes/rag/fusion/normalize.js +135 -0
- package/dist/graph/nodes/rag/index.d.ts +18 -0
- package/dist/graph/nodes/rag/index.js +17 -0
- package/dist/graph/nodes/rag/prompts.d.ts +12 -0
- package/dist/graph/nodes/rag/prompts.js +40 -0
- package/dist/graph/nodes/rag/rag-node.d.ts +21 -0
- package/dist/graph/nodes/rag/rag-node.js +135 -0
- package/dist/graph/nodes/rag/rag-search-tool.d.ts +48 -0
- package/dist/graph/nodes/rag/rag-search-tool.js +230 -0
- package/dist/graph/nodes/rag/react-loop.d.ts +57 -0
- package/dist/graph/nodes/rag/react-loop.js +467 -0
- package/dist/graph/nodes/rag/retrievers/http-embedder.d.ts +29 -0
- package/dist/graph/nodes/rag/retrievers/http-embedder.js +66 -0
- package/dist/graph/nodes/rag/retrievers/in-memory-retriever.d.ts +17 -0
- package/dist/graph/nodes/rag/retrievers/in-memory-retriever.js +18 -0
- package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.d.ts +46 -0
- package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.js +85 -0
- package/dist/graph/nodes/rag/tools.d.ts +90 -0
- package/dist/graph/nodes/rag/tools.js +260 -0
- package/dist/graph/nodes/rag/types.d.ts +226 -0
- package/dist/graph/nodes/rag/types.js +9 -0
- package/dist/graph/nodes/tool-node.d.ts +19 -1
- package/dist/graph/nodes/tool-node.js +158 -33
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/memory/semantic-retrieval.service.d.ts +1 -1
- package/dist/memory/semantic-retrieval.service.js +13 -23
- package/dist/providers/index.d.ts +2 -2
- package/dist/providers/index.js +2 -2
- 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 +7 -2
- package/dist/providers/routing-llm-provider.js +71 -16
- package/dist/registry/deployment-snapshot.js +18 -4
- package/dist/registry/graph-loader.d.ts +2 -0
- package/dist/registry/graph-loader.js +39 -2
- package/dist/registry/preload-flags.d.ts +1 -1
- package/dist/registry/preload-flags.js +1 -1
- package/dist/runtime/agent-runtime.d.ts +5 -0
- package/dist/runtime/agent-runtime.js +107 -70
- package/dist/runtime/dev-rag-deps.d.ts +21 -0
- package/dist/runtime/dev-rag-deps.js +186 -0
- package/dist/runtime/dev-runtime.d.ts +18 -2
- package/dist/runtime/dev-runtime.js +68 -8
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/types.d.ts +10 -9
- package/dist/session/apply-session-compression.d.ts +85 -0
- package/dist/session/apply-session-compression.js +281 -0
- package/dist/session/apply-session-memory.d.ts +21 -0
- package/dist/session/apply-session-memory.js +48 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/persist-turn-messages.d.ts +57 -1
- package/dist/session/persist-turn-messages.js +160 -5
- package/dist/sse/events/index.d.ts +13 -4
- package/dist/sse/events/index.js +9 -1
- package/dist/sse/events/rag-events.d.ts +63 -0
- package/dist/sse/events/rag-events.js +9 -0
- package/dist/sse/events/session-events.d.ts +38 -0
- package/dist/sse/events/session-events.js +4 -0
- package/dist/token/index.d.ts +2 -1
- package/dist/token/index.js +2 -1
- package/dist/token/record-task-token-usage.d.ts +25 -0
- package/dist/token/record-task-token-usage.js +44 -0
- package/dist/token/token-detail-mode.d.ts +4 -4
- package/dist/token/token-detail-mode.js +4 -4
- package/dist/token/token-detail.d.ts +8 -0
- package/dist/token/token-detail.js +22 -0
- package/dist/token/token-stats.service.d.ts +2 -2
- package/dist/token/token-stats.service.js +1 -1
- package/dist/tool/create-tool-executor.d.ts +6 -0
- package/dist/tool/create-tool-executor.js +4 -3
- package/dist/tool/harness-primitives.d.ts +9 -1
- package/dist/tool/harness-primitives.js +43 -12
- package/dist/tool/index.d.ts +2 -1
- package/dist/tool/index.js +2 -1
- package/dist/tool/tool-confirmation.js +8 -5
- package/dist/tool/tool-executor.interface.d.ts +27 -0
- package/dist/tool/tool-orchestrator.d.ts +3 -1
- package/dist/tool/tool-orchestrator.js +15 -6
- package/dist/tool/tool-retry.d.ts +15 -0
- package/dist/tool/tool-retry.js +69 -0
- package/package.json +2 -2
|
@@ -7,13 +7,26 @@
|
|
|
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
|
-
import { mergeModelCallParams } from '@gt-fe/eap-contracts';
|
|
14
|
+
import { AppError, ErrorCodes, mergeModelCallParams } from '@gt-fe/eap-contracts';
|
|
15
15
|
import { EchoLLMProvider } from './echo-llm-provider.js';
|
|
16
16
|
import { DEFAULT_LLM_HTTP_TIMEOUT_MS, HttpLLMProvider } from './http-llm-provider.js';
|
|
17
|
+
export const MODELS_INVOKE_STREAM_MESSAGE = 'EAP_RUNTIME_MODELS_INVOKE 实验口只支持同步 invoke(POST /models/invoke),不支持 stream。关掉该开关,或改走 Foundation /models/select + 本机 HTTP。';
|
|
18
|
+
export function isFoundationModelsInvokeEnabled(env = process.env) {
|
|
19
|
+
const raw = env.EAP_RUNTIME_MODELS_INVOKE;
|
|
20
|
+
return raw === '1' || raw === 'true';
|
|
21
|
+
}
|
|
22
|
+
/** `/models/invoke` 无真流式;开了该开关时 RoutingLLMProvider.stream 必须失败。 */
|
|
23
|
+
export function assertFoundationModelsInvokeAllowsStream(env = process.env) {
|
|
24
|
+
if (!isFoundationModelsInvokeEnabled(env))
|
|
25
|
+
return;
|
|
26
|
+
throw new AppError(ErrorCodes.GRAPH_EXECUTION_ERROR, MODELS_INVOKE_STREAM_MESSAGE, {
|
|
27
|
+
exposeMessage: true,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
17
30
|
export class RoutingLLMProvider {
|
|
18
31
|
foundationUrl;
|
|
19
32
|
fallback;
|
|
@@ -29,6 +42,7 @@ export class RoutingLLMProvider {
|
|
|
29
42
|
return provider.invoke(messages, this.withCallDefaults(config, selected));
|
|
30
43
|
}
|
|
31
44
|
async *stream(messages, config) {
|
|
45
|
+
assertFoundationModelsInvokeAllowsStream();
|
|
32
46
|
const selected = await this.selectModel(config);
|
|
33
47
|
const provider = this.createProviderForModel(selected);
|
|
34
48
|
yield* provider.stream(messages, this.withCallDefaults(config, selected));
|
|
@@ -38,7 +52,7 @@ export class RoutingLLMProvider {
|
|
|
38
52
|
return {
|
|
39
53
|
...config,
|
|
40
54
|
...merged,
|
|
41
|
-
model: selected.name,
|
|
55
|
+
model: selected.name || config.model,
|
|
42
56
|
maxContextTokens: selected.maxContextTokens ?? config.maxContextTokens,
|
|
43
57
|
};
|
|
44
58
|
}
|
|
@@ -70,10 +84,15 @@ export class RoutingLLMProvider {
|
|
|
70
84
|
catch {
|
|
71
85
|
// fallback to local
|
|
72
86
|
}
|
|
73
|
-
return { name: config.model ?? '
|
|
87
|
+
return { name: config.model ?? '', provider: 'local', connection: null };
|
|
88
|
+
}
|
|
89
|
+
useFoundationInvoke() {
|
|
90
|
+
return isFoundationModelsInvokeEnabled();
|
|
74
91
|
}
|
|
75
92
|
createProviderForModel(selected) {
|
|
76
|
-
|
|
93
|
+
if (this.useFoundationInvoke()) {
|
|
94
|
+
return new FoundationModelsInvokeProvider(this.foundationUrl, selected.name, this.turnLog);
|
|
95
|
+
}
|
|
77
96
|
if (selected.connection) {
|
|
78
97
|
return new HttpLLMProvider({
|
|
79
98
|
apiKey: selected.connection.apiKey ?? '',
|
|
@@ -83,16 +102,52 @@ export class RoutingLLMProvider {
|
|
|
83
102
|
turnLog: this.turnLog,
|
|
84
103
|
});
|
|
85
104
|
}
|
|
86
|
-
//
|
|
87
|
-
if (process.env.OPENAI_API_KEY && selected.provider === 'openai') {
|
|
88
|
-
return new HttpLLMProvider({
|
|
89
|
-
apiKey: process.env.OPENAI_API_KEY,
|
|
90
|
-
baseUrl: process.env.OPENAI_BASE_URL,
|
|
91
|
-
defaultModel: selected.name,
|
|
92
|
-
timeoutMs: DEFAULT_LLM_HTTP_TIMEOUT_MS,
|
|
93
|
-
turnLog: this.turnLog,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
105
|
+
// Foundation 未返回连接:与 LocalLlmProvider 同一把钥匙(由调用方注入 fallback)。
|
|
96
106
|
return this.fallback;
|
|
97
107
|
}
|
|
98
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Foundation `/models/invoke` 实验口。默认关。
|
|
111
|
+
* 只实现同步 invoke;stream 拒绝(无真流式、无鉴权头)。
|
|
112
|
+
* Foundation 未提供该接口时不要开 EAP_RUNTIME_MODELS_INVOKE。
|
|
113
|
+
*/
|
|
114
|
+
class FoundationModelsInvokeProvider {
|
|
115
|
+
foundationUrl;
|
|
116
|
+
model;
|
|
117
|
+
turnLog;
|
|
118
|
+
constructor(foundationUrl, model, turnLog) {
|
|
119
|
+
this.foundationUrl = foundationUrl;
|
|
120
|
+
this.model = model;
|
|
121
|
+
this.turnLog = turnLog;
|
|
122
|
+
}
|
|
123
|
+
async invoke(messages, config) {
|
|
124
|
+
const res = await fetch(`${this.foundationUrl}/api/v1/models/invoke`, {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers: { 'Content-Type': 'application/json' },
|
|
127
|
+
body: JSON.stringify({
|
|
128
|
+
model: config.model ?? this.model,
|
|
129
|
+
messages,
|
|
130
|
+
temperature: config.temperature,
|
|
131
|
+
maxTokens: config.maxTokens,
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
throw new Error(`Foundation /models/invoke HTTP ${res.status}`);
|
|
136
|
+
}
|
|
137
|
+
const body = (await res.json());
|
|
138
|
+
const content = body.data?.content ?? body.content ?? '';
|
|
139
|
+
void this.turnLog;
|
|
140
|
+
return {
|
|
141
|
+
content,
|
|
142
|
+
inputTokens: body.data?.inputTokens ?? 0,
|
|
143
|
+
outputTokens: body.data?.outputTokens ?? 0,
|
|
144
|
+
model: body.data?.model ?? config.model ?? this.model,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async *stream(_messages, _config) {
|
|
148
|
+
assertFoundationModelsInvokeAllowsStream();
|
|
149
|
+
throw new AppError(ErrorCodes.GRAPH_EXECUTION_ERROR, MODELS_INVOKE_STREAM_MESSAGE, {
|
|
150
|
+
exposeMessage: true,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -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)。
|
|
@@ -144,6 +144,26 @@ const DEFAULT_GRAPH_DSL = {
|
|
|
144
144
|
finishPoints: ['__end__'],
|
|
145
145
|
config: { timeout: 60000 },
|
|
146
146
|
};
|
|
147
|
+
/** 源图有非空字符串则保留;缺省或空数组为 `['__end__']`。预装 graph.json 与清单内嵌图同一收口。 */
|
|
148
|
+
function finishPointsFromGraphDocument(graph) {
|
|
149
|
+
const raw = graph.finishPoints;
|
|
150
|
+
if (!Array.isArray(raw))
|
|
151
|
+
return ['__end__'];
|
|
152
|
+
const ids = raw
|
|
153
|
+
.filter((id) => typeof id === 'string')
|
|
154
|
+
.map((id) => id.trim())
|
|
155
|
+
.filter((id) => id.length > 0);
|
|
156
|
+
return ids.length > 0 ? ids : ['__end__'];
|
|
157
|
+
}
|
|
158
|
+
const DEFAULT_LOADED_GRAPH_CONFIG = { timeout: 60000 };
|
|
159
|
+
/** 默认 timeout 60000;源图 `config` 覆盖。缺 `maxIterations` 时由引擎用 50。 */
|
|
160
|
+
function configFromGraphDocument(graph) {
|
|
161
|
+
const raw = graph.config;
|
|
162
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
163
|
+
return { ...DEFAULT_LOADED_GRAPH_CONFIG };
|
|
164
|
+
}
|
|
165
|
+
return { ...DEFAULT_LOADED_GRAPH_CONFIG, ...raw };
|
|
166
|
+
}
|
|
147
167
|
function yamlGraphToDsl(agentCode, graph) {
|
|
148
168
|
const nodes = graph.nodes ?? [];
|
|
149
169
|
const edges = graph.edges ?? [];
|
|
@@ -155,8 +175,8 @@ function yamlGraphToDsl(agentCode, graph) {
|
|
|
155
175
|
edges,
|
|
156
176
|
conditionalEdges: graph.conditionalEdges,
|
|
157
177
|
entryPoint: graph.entryPoint ?? nodes[0]?.id ?? 'llm',
|
|
158
|
-
finishPoints:
|
|
159
|
-
config:
|
|
178
|
+
finishPoints: finishPointsFromGraphDocument(graph),
|
|
179
|
+
config: configFromGraphDocument(graph),
|
|
160
180
|
};
|
|
161
181
|
}
|
|
162
182
|
function collectToolNames(agent, graphDsl) {
|
|
@@ -620,6 +640,23 @@ export class GraphLoader {
|
|
|
620
640
|
}
|
|
621
641
|
return this.cloneCached(graph);
|
|
622
642
|
}
|
|
643
|
+
/** Agent `models.default`;无清单或未声明则 undefined。 */
|
|
644
|
+
async resolveDefaultModel(agentCode, version) {
|
|
645
|
+
const requested = this.resolveVersion({ agentVersion: version });
|
|
646
|
+
if (this.deploymentSnapshot) {
|
|
647
|
+
try {
|
|
648
|
+
const snap = await this.deploymentSnapshot.loadAgentSnapshot(agentCode, requested);
|
|
649
|
+
const model = resolveAgentDefaultModel((snap?.manifest ?? {}));
|
|
650
|
+
const trimmed = model?.trim();
|
|
651
|
+
if (trimmed && trimmed.toLowerCase() !== 'default')
|
|
652
|
+
return trimmed;
|
|
653
|
+
}
|
|
654
|
+
catch {
|
|
655
|
+
return undefined;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return undefined;
|
|
659
|
+
}
|
|
623
660
|
async loadGraphFromSource(agentCode, version) {
|
|
624
661
|
if (this.deploymentSnapshot) {
|
|
625
662
|
const snap = await this.deploymentSnapshot.loadAgentSnapshot(agentCode, version);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* |------|------|------|
|
|
6
6
|
* | EAP_PRELOAD_REQUIRE_READY | 关 | 强制校验 .ready |
|
|
7
7
|
* | EAP_PRELOAD_VALIDATE_SKILL_IO | **开** | 执行子图前校验 skill inputDef.required;`0`/`false` 关闭 |
|
|
8
|
-
* | EAP_PRELOAD_REQUIRE_CHECKSUM | 关 | snapshot 必须带 sha256 或 artifacts[]
|
|
8
|
+
* | EAP_PRELOAD_REQUIRE_CHECKSUM | 关 | snapshot 必须带 sha256 或 artifacts[]。开启时摘要不匹配抛 RUNTIME_PRELOAD_PACKAGE_CORRUPT;未开仍 warn |
|
|
9
9
|
* | EAP_PRELOAD_DEFINITION_CACHE | 有预装时开 | 进程内定义缓存;`0`/`false` 关闭 |
|
|
10
10
|
* | EAP_SKILL_HOT_RELOAD | **dev 开 / server 关** | 已装配 Skill 的目录 mtime 变化则丢缓存;未写入装配名单的目录不进清单 |
|
|
11
11
|
* | EAP_LOCAL_SKILLS_DIR | 关 | 开发态作者源 Skill 根;查找优先于 preinstall,低于 users/ |
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* |------|------|------|
|
|
6
6
|
* | EAP_PRELOAD_REQUIRE_READY | 关 | 强制校验 .ready |
|
|
7
7
|
* | EAP_PRELOAD_VALIDATE_SKILL_IO | **开** | 执行子图前校验 skill inputDef.required;`0`/`false` 关闭 |
|
|
8
|
-
* | EAP_PRELOAD_REQUIRE_CHECKSUM | 关 | snapshot 必须带 sha256 或 artifacts[]
|
|
8
|
+
* | EAP_PRELOAD_REQUIRE_CHECKSUM | 关 | snapshot 必须带 sha256 或 artifacts[]。开启时摘要不匹配抛 RUNTIME_PRELOAD_PACKAGE_CORRUPT;未开仍 warn |
|
|
9
9
|
* | EAP_PRELOAD_DEFINITION_CACHE | 有预装时开 | 进程内定义缓存;`0`/`false` 关闭 |
|
|
10
10
|
* | EAP_SKILL_HOT_RELOAD | **dev 开 / server 关** | 已装配 Skill 的目录 mtime 变化则丢缓存;未写入装配名单的目录不进清单 |
|
|
11
11
|
* | EAP_LOCAL_SKILLS_DIR | 关 | 开发态作者源 Skill 根;查找优先于 preinstall,低于 users/ |
|
|
@@ -16,6 +16,7 @@ import type { SessionContextService } from '../session/session-context.service.j
|
|
|
16
16
|
import type { MessageService } from '../session/message.service.js';
|
|
17
17
|
import type { SessionService, SessionRecord, MessageRecord } from '../session/session.service.js';
|
|
18
18
|
import { type ILlmTurnLog, type LlmTurnIndexEntry, type LlmTurnLogKind, type LlmTurnRecord } from '../debug/llm-turn-log.js';
|
|
19
|
+
import { type SessionCompressionOptions } from '../session/apply-session-compression.js';
|
|
19
20
|
import type { SSEEvent } from '../sse/contracts/sse-emitter.contract.js';
|
|
20
21
|
import type { TaskService } from '../task/task.service.js';
|
|
21
22
|
import type { ExecuteParams, ExecuteResult, ForkParams, ForkOverrides, ForkResult, HitlResponse, ResumeHitlResult, SeedSessionMessagesParams, SeedSessionMessagesResult } from './types.js';
|
|
@@ -46,6 +47,10 @@ export interface AgentRuntimeDeps {
|
|
|
46
47
|
*/
|
|
47
48
|
traceCollector?: ITraceCollector;
|
|
48
49
|
recordForkRelation?: RecordForkRelation;
|
|
50
|
+
/** 历史压缩;默认关。见 EAP_RUNTIME_COMPRESSION */
|
|
51
|
+
sessionCompression?: SessionCompressionOptions;
|
|
52
|
+
/** 长期记忆注入;默认关。见 EAP_RUNTIME_MEMORY */
|
|
53
|
+
sessionMemory?: import('../session/apply-session-memory.js').SessionMemoryOptions;
|
|
49
54
|
}
|
|
50
55
|
/** 一次对话详情:消息 + 每步 LLM 入参/响应。File / 内存 / Postgres 同一套结构。 */
|
|
51
56
|
export interface ConversationDetail {
|
|
@@ -7,17 +7,20 @@
|
|
|
7
7
|
* @see 04-runtime-core/design.md §1.5 / §1.7
|
|
8
8
|
*/
|
|
9
9
|
import { randomUUID } from 'node:crypto';
|
|
10
|
-
import { HITL_TIMEOUT_ERROR_CODE, HITL_TIMEOUT_MESSAGE, HitlAlreadyTimedOutError, HitlNotWaitingError, buildHitlWaitedPayload, isHitlDeadlinePassed, readPersistedHitlWaiting, unwrapHitlPaused, } from '../graph/hitl/hitl-handler.js';
|
|
10
|
+
import { HITL_TIMEOUT_ERROR_CODE, HITL_TIMEOUT_MESSAGE, HitlAlreadyTimedOutError, HitlNotWaitingError, buildHitlWaitedPayload, isHitlDeadlinePassed, loadHitlCheckpoint, readPersistedHitlWaiting, unwrapHitlPaused, } from '../graph/hitl/hitl-handler.js';
|
|
11
11
|
import { resolveAgentSystemPromptFromLoader } from '../registry/graph-loader.js';
|
|
12
12
|
import { toLoadedSkillMap } from '../tool/system-prompt-builder.js';
|
|
13
13
|
import { assembleLoadedTools } from '../tool/harness-primitives.js';
|
|
14
14
|
import { createFileLlmTurnLogFromEnv, } from '../debug/llm-turn-log.js';
|
|
15
|
-
import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, buildTurnTranscriptFromStreamEvents, extractHitlThreadId, extractStreamReasoning,
|
|
15
|
+
import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, buildTurnTranscriptFromStreamEvents, extractHitlThreadId, extractStreamReasoning, applyStreamTextDelta, createStreamAssistantBuckets, visibleStreamAssistantText, streamAssistantTexts, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, resolveCheckpointThreadId, appendTerminalToolResult, } from '../session/persist-turn-messages.js';
|
|
16
|
+
import { iterSessionCompression, withCompressionSummaryModel, } from '../session/apply-session-compression.js';
|
|
17
|
+
import { applySessionMemory } from '../session/apply-session-memory.js';
|
|
18
|
+
import { resolveTokenDetail, shouldEmitTokenSse } from '../token/token-detail.js';
|
|
16
19
|
import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
|
|
17
20
|
import { resolveExecutionContext } from '../governance/governance-client.js';
|
|
18
21
|
import { resolveAgentCode } from '../common/agent-code.js';
|
|
19
22
|
import { resolveTraceId } from '../common/trace-id.js';
|
|
20
|
-
import { collectStreamNodeTokenUsage, recordTaskTokenUsages, } from '../token/record-task-token-usage.js';
|
|
23
|
+
import { collectStreamNodeTokenUsage, recordTaskTokenUsages, summarizeTokenUsages, tokenUsagesFromLlmChannel, } from '../token/record-task-token-usage.js';
|
|
21
24
|
import { resolveSkillInputFromInvokeInput } from '../registry/skill-io-guard.js';
|
|
22
25
|
const HITL_TERMINAL_STATUSES = [
|
|
23
26
|
'success',
|
|
@@ -208,6 +211,8 @@ export class AgentRuntime {
|
|
|
208
211
|
executionContext: ctx,
|
|
209
212
|
userId,
|
|
210
213
|
traceId: resolveTraceId(params),
|
|
214
|
+
ragScope: params.ragScope,
|
|
215
|
+
ragSources: params.ragSources,
|
|
211
216
|
}, userId);
|
|
212
217
|
return response.data;
|
|
213
218
|
}
|
|
@@ -274,7 +279,34 @@ export class AgentRuntime {
|
|
|
274
279
|
? (await this.deps.graphLoader.resolveAgentDir(agentCode, params.agentVersion)) ?? undefined
|
|
275
280
|
: undefined;
|
|
276
281
|
const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, params.agentVersion);
|
|
277
|
-
|
|
282
|
+
let history = messageRecordsToGraphMessages(sessionContext.messages);
|
|
283
|
+
const compressionOptions = await withCompressionSummaryModel({
|
|
284
|
+
...this.deps.sessionCompression,
|
|
285
|
+
turnLog: this.deps.sessionCompression?.turnLog ?? this.deps.turnLog,
|
|
286
|
+
}, () => this.deps.graphLoader
|
|
287
|
+
? this.deps.graphLoader.resolveDefaultModel(agentCode, params.agentVersion)
|
|
288
|
+
: Promise.resolve(undefined));
|
|
289
|
+
for await (const step of iterSessionCompression({
|
|
290
|
+
history,
|
|
291
|
+
sessionId,
|
|
292
|
+
userId,
|
|
293
|
+
taskId: task.id,
|
|
294
|
+
options: compressionOptions,
|
|
295
|
+
})) {
|
|
296
|
+
if (step.kind === 'history') {
|
|
297
|
+
history = step.messages;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
yield this.toSSE(step.kind === 'started' ? 'session.compression.started' : 'session.compression.completed', correlationId, task.id, { ...step.payload });
|
|
301
|
+
}
|
|
302
|
+
history = await applySessionMemory({
|
|
303
|
+
history,
|
|
304
|
+
userId,
|
|
305
|
+
agentCode,
|
|
306
|
+
query: typeof params.input === 'string' ? params.input : undefined,
|
|
307
|
+
options: this.deps.sessionMemory,
|
|
308
|
+
});
|
|
309
|
+
const tokenDetail = resolveTokenDetail(params.config?.tokenDetail);
|
|
278
310
|
const messages = await buildTurnGraphMessages({
|
|
279
311
|
history,
|
|
280
312
|
input: params.input,
|
|
@@ -282,7 +314,7 @@ export class AgentRuntime {
|
|
|
282
314
|
threadId: sessionId,
|
|
283
315
|
});
|
|
284
316
|
const startTime = Date.now();
|
|
285
|
-
|
|
317
|
+
const assistantBuckets = createStreamAssistantBuckets();
|
|
286
318
|
let toolCallReasoningContent;
|
|
287
319
|
let assistantReasoningContent;
|
|
288
320
|
const pendingToolCalls = [];
|
|
@@ -300,6 +332,8 @@ export class AgentRuntime {
|
|
|
300
332
|
sessionId,
|
|
301
333
|
executionContext: ctx,
|
|
302
334
|
forceToolServer: params.config?.forceToolServer,
|
|
335
|
+
ragScope: params.ragScope,
|
|
336
|
+
ragSources: params.ragSources,
|
|
303
337
|
loadedTools,
|
|
304
338
|
loadedSkills,
|
|
305
339
|
skillDefinitions,
|
|
@@ -323,35 +357,20 @@ export class AgentRuntime {
|
|
|
323
357
|
toolCallReasoningContent = p.reasoningContent;
|
|
324
358
|
}
|
|
325
359
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (p.toolCallId && p.toolName) {
|
|
329
|
-
toolResults.push({
|
|
330
|
-
toolCallId: p.toolCallId,
|
|
331
|
-
toolName: p.toolName,
|
|
332
|
-
content: p.content,
|
|
333
|
-
success: p.success !== false,
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
}
|
|
360
|
+
// 终态 tool.result / tool.error 回填历史;willRetry 的 error 不进 transcript
|
|
361
|
+
appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
|
|
337
362
|
const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
|
|
338
363
|
if (streamedReasoning)
|
|
339
364
|
assistantReasoningContent = streamedReasoning;
|
|
340
|
-
|
|
341
|
-
if (delta) {
|
|
342
|
-
if (runtimeEvent.type === 'response.final') {
|
|
343
|
-
if (delta.trim())
|
|
344
|
-
assistantText = delta;
|
|
345
|
-
}
|
|
346
|
-
else {
|
|
347
|
-
assistantText += delta;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
365
|
+
applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
|
|
350
366
|
collectStreamNodeTokenUsage(runtimeEvent, nodeTokenUsages);
|
|
351
|
-
|
|
367
|
+
if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
|
|
368
|
+
yield this.runtimeEventToSSE(runtimeEvent, correlationId, task.id);
|
|
369
|
+
}
|
|
352
370
|
}
|
|
353
371
|
await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
|
|
354
372
|
await this.deps.taskService.updateStatus(task.id, 'success');
|
|
373
|
+
const tokenSummary = summarizeTokenUsages(nodeTokenUsages);
|
|
355
374
|
await persistTurnTranscript(this.deps.messageService, {
|
|
356
375
|
sessionId,
|
|
357
376
|
taskId: task.id,
|
|
@@ -359,11 +378,18 @@ export class AgentRuntime {
|
|
|
359
378
|
messages: buildTurnTranscriptFromStreamEvents({
|
|
360
379
|
pendingToolCalls,
|
|
361
380
|
toolResults,
|
|
362
|
-
|
|
381
|
+
assistantTexts: streamAssistantTexts(assistantBuckets),
|
|
382
|
+
assistantText: visibleStreamAssistantText(assistantBuckets),
|
|
383
|
+
finishPoints: graph.finishPoints,
|
|
363
384
|
toolCallReasoningContent,
|
|
364
385
|
assistantReasoningContent,
|
|
365
386
|
}),
|
|
387
|
+
assistantTokenCount: tokenSummary.total.output,
|
|
388
|
+
assistantDurationMs: Date.now() - startTime,
|
|
366
389
|
});
|
|
390
|
+
if (shouldEmitTokenSse(tokenDetail, 'stats.token_usage')) {
|
|
391
|
+
yield this.toSSE('stats.token_usage', correlationId, task.id, tokenSummary);
|
|
392
|
+
}
|
|
367
393
|
yield this.toSSE('task.completed', correlationId, task.id, {
|
|
368
394
|
taskId: task.id,
|
|
369
395
|
duration: Date.now() - startTime,
|
|
@@ -390,6 +416,7 @@ export class AgentRuntime {
|
|
|
390
416
|
return;
|
|
391
417
|
}
|
|
392
418
|
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
419
|
+
await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
|
|
393
420
|
await this.deps.taskService.updateStatus(task.id, 'failed', message);
|
|
394
421
|
yield this.toSSE('task.error', correlationId, task.id, {
|
|
395
422
|
code: 'runtime.graph.execution_error',
|
|
@@ -409,7 +436,7 @@ export class AgentRuntime {
|
|
|
409
436
|
let output = '';
|
|
410
437
|
let waiting;
|
|
411
438
|
let sawWait = false;
|
|
412
|
-
|
|
439
|
+
const assistantBuckets = createStreamAssistantBuckets();
|
|
413
440
|
for await (const event of this.streamResumeHitl(taskId, response)) {
|
|
414
441
|
if (event.type === 'hitl.waited') {
|
|
415
442
|
sawWait = true;
|
|
@@ -421,21 +448,12 @@ export class AgentRuntime {
|
|
|
421
448
|
};
|
|
422
449
|
output = waiting.question;
|
|
423
450
|
}
|
|
424
|
-
|
|
425
|
-
if (delta) {
|
|
426
|
-
if (event.type === 'response.final') {
|
|
427
|
-
if (delta.trim())
|
|
428
|
-
assistantText = delta;
|
|
429
|
-
}
|
|
430
|
-
else {
|
|
431
|
-
assistantText += delta;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
451
|
+
applyStreamTextDelta(assistantBuckets, event.type, event.data);
|
|
434
452
|
}
|
|
435
453
|
if (sawWait) {
|
|
436
454
|
return { taskId, status: 'waiting_hitl', output, hitl: waiting };
|
|
437
455
|
}
|
|
438
|
-
return { taskId, status: 'success', output:
|
|
456
|
+
return { taskId, status: 'success', output: visibleStreamAssistantText(assistantBuckets) || output };
|
|
439
457
|
}
|
|
440
458
|
/**
|
|
441
459
|
* HITL 恢复的流式入口:新开一条 SSE(第一条 stream 在 `hitl.waited` 后已结束)。
|
|
@@ -465,6 +483,7 @@ export class AgentRuntime {
|
|
|
465
483
|
if (!claimed) {
|
|
466
484
|
throw new AppError(ErrorCodes.TASK_HITL_INVALID_STATE, `Task ${taskId} is no longer waiting for HITL`, { exposeMessage: true });
|
|
467
485
|
}
|
|
486
|
+
const tokenDetail = resolveTokenDetail(response.tokenDetail);
|
|
468
487
|
yield this.toSSE('hitl.resumed', correlationId, taskId, {
|
|
469
488
|
choice: response.choice,
|
|
470
489
|
userId: response.userId,
|
|
@@ -516,7 +535,7 @@ export class AgentRuntime {
|
|
|
516
535
|
traceCollector: this.deps.traceCollector,
|
|
517
536
|
};
|
|
518
537
|
const startTime = Date.now();
|
|
519
|
-
|
|
538
|
+
const assistantBuckets = createStreamAssistantBuckets();
|
|
520
539
|
let toolCallReasoningContent;
|
|
521
540
|
let assistantReasoningContent;
|
|
522
541
|
const pendingToolCalls = [];
|
|
@@ -537,35 +556,19 @@ export class AgentRuntime {
|
|
|
537
556
|
toolCallReasoningContent = p.reasoningContent;
|
|
538
557
|
}
|
|
539
558
|
}
|
|
540
|
-
|
|
541
|
-
const p = runtimeEvent.payload;
|
|
542
|
-
if (p.toolCallId && p.toolName) {
|
|
543
|
-
toolResults.push({
|
|
544
|
-
toolCallId: p.toolCallId,
|
|
545
|
-
toolName: p.toolName,
|
|
546
|
-
content: p.content ?? p.error,
|
|
547
|
-
success: runtimeEvent.type === 'tool.result' && p.success !== false,
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
}
|
|
559
|
+
appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
|
|
551
560
|
const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
|
|
552
561
|
if (streamedReasoning)
|
|
553
562
|
assistantReasoningContent = streamedReasoning;
|
|
554
|
-
|
|
555
|
-
if (delta) {
|
|
556
|
-
if (runtimeEvent.type === 'response.final') {
|
|
557
|
-
if (delta.trim())
|
|
558
|
-
assistantText = delta;
|
|
559
|
-
}
|
|
560
|
-
else {
|
|
561
|
-
assistantText += delta;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
563
|
+
applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
|
|
564
564
|
collectStreamNodeTokenUsage(runtimeEvent, nodeTokenUsages);
|
|
565
|
-
|
|
565
|
+
if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
|
|
566
|
+
yield this.runtimeEventToSSE(runtimeEvent, correlationId, taskId);
|
|
567
|
+
}
|
|
566
568
|
}
|
|
567
569
|
await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
|
|
568
|
-
await this.deps.taskService.updateStatus(taskId, 'success',
|
|
570
|
+
await this.deps.taskService.updateStatus(taskId, 'success', visibleStreamAssistantText(assistantBuckets) || undefined);
|
|
571
|
+
const tokenSummary = summarizeTokenUsages(nodeTokenUsages);
|
|
569
572
|
if (sessionId) {
|
|
570
573
|
await persistTurnTranscript(this.deps.messageService, {
|
|
571
574
|
sessionId,
|
|
@@ -574,12 +577,19 @@ export class AgentRuntime {
|
|
|
574
577
|
messages: buildTurnTranscriptFromStreamEvents({
|
|
575
578
|
pendingToolCalls,
|
|
576
579
|
toolResults,
|
|
577
|
-
|
|
580
|
+
assistantTexts: streamAssistantTexts(assistantBuckets),
|
|
581
|
+
assistantText: visibleStreamAssistantText(assistantBuckets),
|
|
582
|
+
finishPoints: graph.finishPoints,
|
|
578
583
|
toolCallReasoningContent,
|
|
579
584
|
assistantReasoningContent,
|
|
580
585
|
}),
|
|
586
|
+
assistantTokenCount: tokenSummary.total.output,
|
|
587
|
+
assistantDurationMs: Date.now() - startTime,
|
|
581
588
|
});
|
|
582
589
|
}
|
|
590
|
+
if (shouldEmitTokenSse(tokenDetail, 'stats.token_usage')) {
|
|
591
|
+
yield this.toSSE('stats.token_usage', correlationId, taskId, tokenSummary);
|
|
592
|
+
}
|
|
583
593
|
yield this.toSSE('task.completed', correlationId, taskId, {
|
|
584
594
|
taskId,
|
|
585
595
|
duration: Date.now() - startTime,
|
|
@@ -609,6 +619,7 @@ export class AgentRuntime {
|
|
|
609
619
|
}
|
|
610
620
|
const appErr = err instanceof AppError ? err : AppError.fromUnknown(err);
|
|
611
621
|
const failMsg = appErr.exposeMessage ? appErr.message : '系统内部错误';
|
|
622
|
+
await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
|
|
612
623
|
await this.deps.taskService.updateStatus(taskId, 'failed', failMsg);
|
|
613
624
|
yield this.toSSE('task.error', correlationId, taskId, {
|
|
614
625
|
code: appErr.code,
|
|
@@ -671,8 +682,14 @@ export class AgentRuntime {
|
|
|
671
682
|
? forkedCheckpoint.state.agentSystemPrompt
|
|
672
683
|
: undefined);
|
|
673
684
|
try {
|
|
685
|
+
const forkState = { ...forkedCheckpoint.state };
|
|
686
|
+
delete forkState.toolSpanId;
|
|
687
|
+
delete forkState.hitlToolCallId;
|
|
674
688
|
const result = await this.deps.graphEngine.invoke(graph, {
|
|
675
|
-
|
|
689
|
+
messages: Array.isArray(forkState.messages)
|
|
690
|
+
? forkState.messages
|
|
691
|
+
: [],
|
|
692
|
+
...forkState,
|
|
676
693
|
agentVersion,
|
|
677
694
|
agentDir,
|
|
678
695
|
agentSystemPrompt,
|
|
@@ -729,6 +746,20 @@ export class AgentRuntime {
|
|
|
729
746
|
}
|
|
730
747
|
catch (err) {
|
|
731
748
|
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
749
|
+
const sessionId = typeof forkedCheckpoint.state.sessionId === 'string'
|
|
750
|
+
? forkedCheckpoint.state.sessionId
|
|
751
|
+
: undefined;
|
|
752
|
+
const threadId = resolveCheckpointThreadId({
|
|
753
|
+
sessionId,
|
|
754
|
+
taskId: task.id,
|
|
755
|
+
});
|
|
756
|
+
const ck = await this.deps.checkpointSaver.load(threadId);
|
|
757
|
+
const baseline = tokenUsagesFromLlmChannel(forkedCheckpoint.state);
|
|
758
|
+
const latest = tokenUsagesFromLlmChannel(ck?.state ?? forkedCheckpoint.state);
|
|
759
|
+
const usages = latest.length >= baseline.length ? latest.slice(baseline.length) : latest;
|
|
760
|
+
await recordTaskTokenUsages(this.deps.taskService, task.id, usages);
|
|
761
|
+
const input_tokens = usages.reduce((sum, row) => sum + row.inputTokens, 0);
|
|
762
|
+
const output_tokens = usages.reduce((sum, row) => sum + row.outputTokens, 0);
|
|
732
763
|
await this.deps.taskService.updateStatus(task.id, 'failed', message);
|
|
733
764
|
return {
|
|
734
765
|
newTraceId: traceId,
|
|
@@ -737,7 +768,11 @@ export class AgentRuntime {
|
|
|
737
768
|
checkpointId,
|
|
738
769
|
status: 'failed',
|
|
739
770
|
output: message,
|
|
740
|
-
tokenUsage: {
|
|
771
|
+
tokenUsage: {
|
|
772
|
+
input_tokens,
|
|
773
|
+
output_tokens,
|
|
774
|
+
total_tokens: input_tokens + output_tokens,
|
|
775
|
+
},
|
|
741
776
|
};
|
|
742
777
|
}
|
|
743
778
|
}
|
|
@@ -758,16 +793,18 @@ export class AgentRuntime {
|
|
|
758
793
|
const sessionId = typeof task.sessionId === 'string' && task.sessionId.trim()
|
|
759
794
|
? task.sessionId.trim()
|
|
760
795
|
: undefined;
|
|
761
|
-
const
|
|
796
|
+
const hinted = waiting?.threadId ??
|
|
762
797
|
(typeof response.threadId === 'string' && response.threadId.trim()
|
|
763
798
|
? response.threadId.trim()
|
|
764
799
|
: undefined) ??
|
|
765
800
|
extractHitlThreadId(task.output) ??
|
|
766
801
|
sessionId;
|
|
767
|
-
if (!
|
|
802
|
+
if (!hinted) {
|
|
768
803
|
throw new AppError(ErrorCodes.TASK_HITL_INVALID_STATE, `Task ${taskId} 缺少 sessionId/hitlThreadId,无法冷恢复 HITL Checkpoint`, { exposeMessage: true });
|
|
769
804
|
}
|
|
770
|
-
const
|
|
805
|
+
const resolved = await loadHitlCheckpoint(this.deps.checkpointSaver, hinted);
|
|
806
|
+
const threadId = resolved.threadId;
|
|
807
|
+
const checkpoint = resolved.checkpoint;
|
|
771
808
|
const persisted = readPersistedHitlWaiting(checkpoint?.state);
|
|
772
809
|
const behavior = persisted?.config.timeoutBehavior ?? 'error';
|
|
773
810
|
if (persisted &&
|