@cicctencent/agent-core 0.1.20 → 0.1.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/dist/cjs/a2a/index.d.ts +4 -0
- package/dist/cjs/a2a/index.d.ts.map +1 -1
- package/dist/cjs/a2a/index.js +6 -1
- package/dist/cjs/a2a/index.js.map +1 -1
- package/dist/cjs/a2a/rest-client.d.ts +111 -0
- package/dist/cjs/a2a/rest-client.d.ts.map +1 -0
- package/dist/cjs/a2a/rest-client.js +342 -0
- package/dist/cjs/a2a/rest-client.js.map +1 -0
- package/dist/cjs/a2a/rest-runner.d.ts +41 -0
- package/dist/cjs/a2a/rest-runner.d.ts.map +1 -0
- package/dist/cjs/a2a/rest-runner.js +188 -0
- package/dist/cjs/a2a/rest-runner.js.map +1 -0
- package/dist/cjs/agent/engine.d.ts +17 -0
- package/dist/cjs/agent/engine.d.ts.map +1 -1
- package/dist/cjs/agent/engine.js +208 -13
- package/dist/cjs/agent/engine.js.map +1 -1
- package/dist/cjs/agent/llm-caller.d.ts +12 -1
- package/dist/cjs/agent/llm-caller.d.ts.map +1 -1
- package/dist/cjs/agent/llm-caller.js +101 -32
- package/dist/cjs/agent/llm-caller.js.map +1 -1
- package/dist/cjs/agent/types.d.ts +1 -1
- package/dist/cjs/agent/types.d.ts.map +1 -1
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +8 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/kb/in-memory-kb.d.ts.map +1 -1
- package/dist/cjs/kb/in-memory-kb.js +2 -1
- package/dist/cjs/kb/in-memory-kb.js.map +1 -1
- package/dist/cjs/llm/anthropic-provider.d.ts.map +1 -1
- package/dist/cjs/llm/anthropic-provider.js +1 -0
- package/dist/cjs/llm/anthropic-provider.js.map +1 -1
- package/dist/cjs/llm/ollama-provider.d.ts.map +1 -1
- package/dist/cjs/llm/ollama-provider.js +1 -0
- package/dist/cjs/llm/ollama-provider.js.map +1 -1
- package/dist/cjs/llm/openai-provider.d.ts.map +1 -1
- package/dist/cjs/llm/openai-provider.js +17 -12
- package/dist/cjs/llm/openai-provider.js.map +1 -1
- package/dist/cjs/llm/types.d.ts +4 -0
- package/dist/cjs/llm/types.d.ts.map +1 -1
- package/dist/cjs/llm/types.js.map +1 -1
- package/dist/cjs/memory/checkpoint.d.ts.map +1 -1
- package/dist/cjs/memory/checkpoint.js +3 -2
- package/dist/cjs/memory/checkpoint.js.map +1 -1
- package/dist/cjs/memory/extractor.d.ts +7 -0
- package/dist/cjs/memory/extractor.d.ts.map +1 -1
- package/dist/cjs/memory/extractor.js +18 -1
- package/dist/cjs/memory/extractor.js.map +1 -1
- package/dist/cjs/memory/persona.d.ts +4 -0
- package/dist/cjs/memory/persona.d.ts.map +1 -1
- package/dist/cjs/memory/persona.js +15 -1
- package/dist/cjs/memory/persona.js.map +1 -1
- package/dist/cjs/memory/pipeline.d.ts +3 -0
- package/dist/cjs/memory/pipeline.d.ts.map +1 -1
- package/dist/cjs/memory/pipeline.js +7 -4
- package/dist/cjs/memory/pipeline.js.map +1 -1
- package/dist/cjs/memory/scene.d.ts +4 -0
- package/dist/cjs/memory/scene.d.ts.map +1 -1
- package/dist/cjs/memory/scene.js +15 -1
- package/dist/cjs/memory/scene.js.map +1 -1
- package/dist/cjs/middleware/audit-log.d.ts.map +1 -1
- package/dist/cjs/middleware/audit-log.js +3 -2
- package/dist/cjs/middleware/audit-log.js.map +1 -1
- package/dist/cjs/store/file-tool-result-store.d.ts.map +1 -1
- package/dist/cjs/store/file-tool-result-store.js +2 -1
- package/dist/cjs/store/file-tool-result-store.js.map +1 -1
- package/dist/cjs/store/json-store.d.ts +5 -1
- package/dist/cjs/store/json-store.d.ts.map +1 -1
- package/dist/cjs/store/json-store.js +5 -1
- package/dist/cjs/store/json-store.js.map +1 -1
- package/dist/cjs/tool/delegate.js +1 -1
- package/dist/cjs/tool/delegate.js.map +1 -1
- package/dist/cjs/utils/date.d.ts +36 -0
- package/dist/cjs/utils/date.d.ts.map +1 -0
- package/dist/cjs/utils/date.js +52 -0
- package/dist/cjs/utils/date.js.map +1 -0
- package/dist/esm/a2a/index.d.ts +4 -0
- package/dist/esm/a2a/index.d.ts.map +1 -1
- package/dist/esm/a2a/index.js +3 -0
- package/dist/esm/a2a/index.js.map +1 -1
- package/dist/esm/a2a/rest-client.d.ts +111 -0
- package/dist/esm/a2a/rest-client.d.ts.map +1 -0
- package/dist/esm/a2a/rest-client.js +338 -0
- package/dist/esm/a2a/rest-client.js.map +1 -0
- package/dist/esm/a2a/rest-runner.d.ts +41 -0
- package/dist/esm/a2a/rest-runner.d.ts.map +1 -0
- package/dist/esm/a2a/rest-runner.js +185 -0
- package/dist/esm/a2a/rest-runner.js.map +1 -0
- package/dist/esm/agent/engine.d.ts +17 -0
- package/dist/esm/agent/engine.d.ts.map +1 -1
- package/dist/esm/agent/engine.js +208 -13
- package/dist/esm/agent/engine.js.map +1 -1
- package/dist/esm/agent/llm-caller.d.ts +12 -1
- package/dist/esm/agent/llm-caller.d.ts.map +1 -1
- package/dist/esm/agent/llm-caller.js +101 -32
- package/dist/esm/agent/llm-caller.js.map +1 -1
- package/dist/esm/agent/types.d.ts +1 -1
- package/dist/esm/agent/types.d.ts.map +1 -1
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/kb/in-memory-kb.d.ts.map +1 -1
- package/dist/esm/kb/in-memory-kb.js +2 -1
- package/dist/esm/kb/in-memory-kb.js.map +1 -1
- package/dist/esm/llm/anthropic-provider.d.ts.map +1 -1
- package/dist/esm/llm/anthropic-provider.js +1 -0
- package/dist/esm/llm/anthropic-provider.js.map +1 -1
- package/dist/esm/llm/ollama-provider.d.ts.map +1 -1
- package/dist/esm/llm/ollama-provider.js +1 -0
- package/dist/esm/llm/ollama-provider.js.map +1 -1
- package/dist/esm/llm/openai-provider.d.ts.map +1 -1
- package/dist/esm/llm/openai-provider.js +17 -12
- package/dist/esm/llm/openai-provider.js.map +1 -1
- package/dist/esm/llm/types.d.ts +4 -0
- package/dist/esm/llm/types.d.ts.map +1 -1
- package/dist/esm/llm/types.js.map +1 -1
- package/dist/esm/memory/checkpoint.d.ts.map +1 -1
- package/dist/esm/memory/checkpoint.js +3 -2
- package/dist/esm/memory/checkpoint.js.map +1 -1
- package/dist/esm/memory/extractor.d.ts +7 -0
- package/dist/esm/memory/extractor.d.ts.map +1 -1
- package/dist/esm/memory/extractor.js +18 -1
- package/dist/esm/memory/extractor.js.map +1 -1
- package/dist/esm/memory/persona.d.ts +4 -0
- package/dist/esm/memory/persona.d.ts.map +1 -1
- package/dist/esm/memory/persona.js +15 -1
- package/dist/esm/memory/persona.js.map +1 -1
- package/dist/esm/memory/pipeline.d.ts +3 -0
- package/dist/esm/memory/pipeline.d.ts.map +1 -1
- package/dist/esm/memory/pipeline.js +7 -4
- package/dist/esm/memory/pipeline.js.map +1 -1
- package/dist/esm/memory/scene.d.ts +4 -0
- package/dist/esm/memory/scene.d.ts.map +1 -1
- package/dist/esm/memory/scene.js +15 -1
- package/dist/esm/memory/scene.js.map +1 -1
- package/dist/esm/middleware/audit-log.d.ts.map +1 -1
- package/dist/esm/middleware/audit-log.js +3 -2
- package/dist/esm/middleware/audit-log.js.map +1 -1
- package/dist/esm/store/file-tool-result-store.d.ts.map +1 -1
- package/dist/esm/store/file-tool-result-store.js +2 -1
- package/dist/esm/store/file-tool-result-store.js.map +1 -1
- package/dist/esm/store/json-store.d.ts +5 -1
- package/dist/esm/store/json-store.d.ts.map +1 -1
- package/dist/esm/store/json-store.js +5 -1
- package/dist/esm/store/json-store.js.map +1 -1
- package/dist/esm/tool/delegate.js +1 -1
- package/dist/esm/tool/delegate.js.map +1 -1
- package/dist/esm/utils/date.d.ts +36 -0
- package/dist/esm/utils/date.d.ts.map +1 -0
- package/dist/esm/utils/date.js +48 -0
- package/dist/esm/utils/date.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// REST 风格 A2A Remote Runner — 将 REST 风格远程 Agent 包装为 SubAgentRunner
|
|
3
|
+
// 供 delegate_task 使用,与标准 JSON-RPC A2A Runner 并行存在
|
|
4
|
+
// ============================================================
|
|
5
|
+
/**
|
|
6
|
+
* 创建 REST 风格远程 A2A Agent 的 SubAgentRunner
|
|
7
|
+
*
|
|
8
|
+
* 核心逻辑:
|
|
9
|
+
* - streaming=true && ctx.emitSubEvent 存在 → invokeStreaming + 事件转发
|
|
10
|
+
* - 否则 → invoke 同步阻塞
|
|
11
|
+
*
|
|
12
|
+
* 事件映射:
|
|
13
|
+
* - status(running) → start
|
|
14
|
+
* - stage → progress
|
|
15
|
+
* - tool_call → tool_call
|
|
16
|
+
* - tool_result → tool_result
|
|
17
|
+
* - message_chunk → content
|
|
18
|
+
* - result → done(由 runStreaming 提取最终文本)
|
|
19
|
+
* - error → error
|
|
20
|
+
*/
|
|
21
|
+
export function createRestA2ARemoteRunner(config) {
|
|
22
|
+
const { baseUrl, capabilityId, name: runnerName, description: runnerDesc, client, staticFields = {}, taskField = 'analysis_focus', streaming = true, fallbackToSync = true, } = config;
|
|
23
|
+
/** 将 task 字符串构建为请求输入 */
|
|
24
|
+
function buildInput(task) {
|
|
25
|
+
return { ...staticFields, [taskField]: task };
|
|
26
|
+
}
|
|
27
|
+
/** 从 RestCapabilityResult 中提取最终文本 */
|
|
28
|
+
function extractResultText(result) {
|
|
29
|
+
if (result.result?.final_text)
|
|
30
|
+
return result.result.final_text;
|
|
31
|
+
if (result.result?.summary)
|
|
32
|
+
return result.result.summary;
|
|
33
|
+
// 降级:JSON 序列化整个 result
|
|
34
|
+
return JSON.stringify(result.result || result, null, 2);
|
|
35
|
+
}
|
|
36
|
+
// ---- 同步执行 ----
|
|
37
|
+
const runSync = async (task) => {
|
|
38
|
+
try {
|
|
39
|
+
const result = await client.invoke(baseUrl, capabilityId, buildInput(task));
|
|
40
|
+
if (result.status === 'failed' || result.error) {
|
|
41
|
+
const errMsg = result.error || 'Unknown error';
|
|
42
|
+
console.error(`[REST-A2A] '${runnerName}' sync failed: ${errMsg}`);
|
|
43
|
+
return `[REST A2A '${runnerName}'] Error: ${errMsg}`;
|
|
44
|
+
}
|
|
45
|
+
return extractResultText(result);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
console.error(`[REST-A2A] '${runnerName}' sync error: ${err?.message || err}`);
|
|
49
|
+
return `[REST A2A '${runnerName}'] Error: ${err.message || err}`;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
// ---- 流式执行 ----
|
|
53
|
+
const runStreaming = async (task, ctx) => {
|
|
54
|
+
const emit = ctx.emitSubEvent;
|
|
55
|
+
const parentCallId = ctx.currentToolCallId || `rest_a2a_${Date.now()}`;
|
|
56
|
+
emitStart(emit, parentCallId, runnerName, task);
|
|
57
|
+
try {
|
|
58
|
+
const finalResult = await client.invokeStreaming(baseUrl, capabilityId, buildInput(task), (event) => forwardRestSSEEvent(emit, parentCallId, runnerName, event), ctx.abortSignal);
|
|
59
|
+
if (finalResult?.status === 'failed' || finalResult?.error) {
|
|
60
|
+
const errMsg = finalResult.error || 'Remote agent returned error';
|
|
61
|
+
console.error(`[REST-A2A] '${runnerName}' failed: ${errMsg}`);
|
|
62
|
+
emitError(emit, parentCallId, runnerName, errMsg);
|
|
63
|
+
return `[REST A2A '${runnerName}'] Error: ${errMsg}`;
|
|
64
|
+
}
|
|
65
|
+
const resultText = finalResult
|
|
66
|
+
? extractResultText(finalResult)
|
|
67
|
+
: 'Task completed with no output.';
|
|
68
|
+
emitDone(emit, parentCallId, runnerName, resultText.length);
|
|
69
|
+
return resultText;
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
const errMsg = err?.message || String(err);
|
|
73
|
+
console.error(`[REST-A2A] '${runnerName}' streaming error: ${errMsg}`);
|
|
74
|
+
// 流式失败,自动回退到同步模式
|
|
75
|
+
if (fallbackToSync) {
|
|
76
|
+
console.log(`[REST-A2A] '${runnerName}' falling back to sync`);
|
|
77
|
+
return runSync(task);
|
|
78
|
+
}
|
|
79
|
+
emitError(emit, parentCallId, runnerName, errMsg);
|
|
80
|
+
return `[REST A2A '${runnerName}'] Error: ${errMsg}`;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
name: runnerName,
|
|
85
|
+
description: runnerDesc,
|
|
86
|
+
run: async (task, ctx, _options) => {
|
|
87
|
+
return streaming && ctx?.emitSubEvent
|
|
88
|
+
? runStreaming(task, ctx)
|
|
89
|
+
: runSync(task);
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// ---- 事件转发辅助函数 ----
|
|
94
|
+
function emitStart(emit, parentCallId, agentName, task) {
|
|
95
|
+
emit?.({
|
|
96
|
+
parentToolCallId: parentCallId,
|
|
97
|
+
agentName,
|
|
98
|
+
eventType: 'start',
|
|
99
|
+
data: { agentName, task, source: 'rest-a2a-streaming' },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function emitDone(emit, parentCallId, agentName, contentLength) {
|
|
103
|
+
emit?.({
|
|
104
|
+
parentToolCallId: parentCallId,
|
|
105
|
+
agentName,
|
|
106
|
+
eventType: 'done',
|
|
107
|
+
data: { contentLength },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function emitError(emit, parentCallId, agentName, error) {
|
|
111
|
+
emit?.({
|
|
112
|
+
parentToolCallId: parentCallId,
|
|
113
|
+
agentName,
|
|
114
|
+
eventType: 'error',
|
|
115
|
+
data: { error },
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 将 REST 风格 SSE 事件转发为 SubAgentEvent
|
|
120
|
+
*
|
|
121
|
+
* 事件映射:
|
|
122
|
+
* status(running) → progress
|
|
123
|
+
* stage → progress
|
|
124
|
+
* tool_call → tool_call
|
|
125
|
+
* tool_result → tool_result
|
|
126
|
+
* message_chunk → content
|
|
127
|
+
* result → (由 runStreaming 处理,不转发)
|
|
128
|
+
* error → error
|
|
129
|
+
*/
|
|
130
|
+
function forwardRestSSEEvent(emit, parentCallId, agentName, event) {
|
|
131
|
+
const { event: eventType, data } = event;
|
|
132
|
+
switch (eventType) {
|
|
133
|
+
case 'status':
|
|
134
|
+
// running → progress;completed → 由 result 事件处理
|
|
135
|
+
if (data.status === 'running') {
|
|
136
|
+
emit?.({
|
|
137
|
+
parentToolCallId: parentCallId,
|
|
138
|
+
agentName,
|
|
139
|
+
eventType: 'progress',
|
|
140
|
+
data: { status: 'running', trace_id: data.trace_id },
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
case 'stage':
|
|
145
|
+
emit?.({
|
|
146
|
+
parentToolCallId: parentCallId,
|
|
147
|
+
agentName,
|
|
148
|
+
eventType: 'progress',
|
|
149
|
+
data: { stage: data.stage, trace_id: data.trace_id },
|
|
150
|
+
});
|
|
151
|
+
break;
|
|
152
|
+
case 'tool_call':
|
|
153
|
+
emit?.({
|
|
154
|
+
parentToolCallId: parentCallId,
|
|
155
|
+
agentName,
|
|
156
|
+
eventType: 'tool_call',
|
|
157
|
+
data,
|
|
158
|
+
});
|
|
159
|
+
break;
|
|
160
|
+
case 'tool_result':
|
|
161
|
+
emit?.({
|
|
162
|
+
parentToolCallId: parentCallId,
|
|
163
|
+
agentName,
|
|
164
|
+
eventType: 'tool_result',
|
|
165
|
+
data,
|
|
166
|
+
});
|
|
167
|
+
break;
|
|
168
|
+
case 'message_chunk':
|
|
169
|
+
emit?.({
|
|
170
|
+
parentToolCallId: parentCallId,
|
|
171
|
+
agentName,
|
|
172
|
+
eventType: 'content',
|
|
173
|
+
data: { content: data.content },
|
|
174
|
+
});
|
|
175
|
+
break;
|
|
176
|
+
// result 和 error 由 runStreaming 的 finalResult 处理,不在此转发
|
|
177
|
+
case 'result':
|
|
178
|
+
case 'error':
|
|
179
|
+
break;
|
|
180
|
+
default:
|
|
181
|
+
// 未知事件类型 → 丢弃
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=rest-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rest-runner.js","sourceRoot":"","sources":["../../../src/a2a/rest-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -68,6 +68,23 @@ export declare class AgentEngine {
|
|
|
68
68
|
private resolveSkill;
|
|
69
69
|
/** 构建初始消息列表(system + history + user) */
|
|
70
70
|
private buildInitialMessages;
|
|
71
|
+
/**
|
|
72
|
+
* 将对话历史压缩为摘要,用 <conversation_history> XML 标签包裹,
|
|
73
|
+
* 注入为 user 消息 + assistant 确认对。
|
|
74
|
+
*
|
|
75
|
+
* 摘要是陈述句,当前消息是指令句,语义上天然区分。
|
|
76
|
+
* ≤2 条消息时直接拼接,省去 LLM 调用;LLM 失败时回退到简单拼接。
|
|
77
|
+
*
|
|
78
|
+
* @param history 历史消息列表
|
|
79
|
+
* @param currentText 当前用户消息文本(用于排除最后一条匹配的 user 消息,null 表示全部纳入摘要)
|
|
80
|
+
* @returns 摘要消息对 [user: <conversation_history>..., assistant: 确认]
|
|
81
|
+
*/
|
|
82
|
+
private buildHistoryMessages;
|
|
83
|
+
/**
|
|
84
|
+
* 通过轻量 LLM 调用将多轮对话历史压缩为 ≤800 字的陈述句摘要。
|
|
85
|
+
* LLM 失败时回退到简单拼接。
|
|
86
|
+
*/
|
|
87
|
+
private summarizeHistory;
|
|
71
88
|
/** 获取工具定义列表(含预过滤 + 渐进式发现) */
|
|
72
89
|
private resolveToolDefs;
|
|
73
90
|
/** 构建 ToolContext */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/agent/engine.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAoE,MAAM,aAAa,CAAC;AAItI,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAe,MAAM,YAAY,CAAC;AAEvE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/agent/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/agent/engine.js
CHANGED
|
@@ -36,6 +36,43 @@ function buildSkillCatalog(skillManager) {
|
|
|
36
36
|
const lines = manifests.map(m => `- \`${m.id}\`: ${m.name} — ${m.description || '(no description)'}`);
|
|
37
37
|
return `## Available Skills\n\nYou have access to the following skills. Use \`load_skill(skill_id="<id>")\` to load full instructions when a task matches a skill's purpose.\n\n${lines.join('\n')}`;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* 生成工具调用的简短摘要(用于历史摘要)。
|
|
41
|
+
* 保留工具名和关键参数,让 LLM 在后续轮次中能复用之前的调用参数。
|
|
42
|
+
*/
|
|
43
|
+
function summarizeToolCall(name, args) {
|
|
44
|
+
switch (name) {
|
|
45
|
+
case 'send_email': {
|
|
46
|
+
const to = Array.isArray(args.to) ? args.to.join(',') : (args.to || '');
|
|
47
|
+
const subject = (args.subject || '').slice(0, 60);
|
|
48
|
+
const hasAttachments = Array.isArray(args.attachments) && args.attachments.length > 0;
|
|
49
|
+
return `send_email(to=${to}, subject="${subject}"${hasAttachments ? ', with_attachments' : ''})`;
|
|
50
|
+
}
|
|
51
|
+
case 'delegate_task': {
|
|
52
|
+
const agent = args.agent || '';
|
|
53
|
+
const task = typeof args.task === 'string' ? args.task.slice(0, 80) : '';
|
|
54
|
+
return `delegate_task(agent=${agent}, task="${task}...")`;
|
|
55
|
+
}
|
|
56
|
+
case 'create_report':
|
|
57
|
+
case 'create_presentation':
|
|
58
|
+
case 'create_document':
|
|
59
|
+
case 'create_pdf':
|
|
60
|
+
case 'save_artifact': {
|
|
61
|
+
const filename = (args.filename || args.title || '').slice(0, 60);
|
|
62
|
+
return `${name}(filename="${filename}")`;
|
|
63
|
+
}
|
|
64
|
+
default: {
|
|
65
|
+
// 通用:展示前 3 个参数的键名和值摘要
|
|
66
|
+
const keys = Object.keys(args).slice(0, 3);
|
|
67
|
+
const parts = keys.map(k => {
|
|
68
|
+
const v = args[k];
|
|
69
|
+
const s = typeof v === 'string' ? v.slice(0, 50) : JSON.stringify(v)?.slice(0, 50);
|
|
70
|
+
return `${k}=${s}`;
|
|
71
|
+
});
|
|
72
|
+
return `${name}(${parts.join(', ')})`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
39
76
|
export class AgentEngine {
|
|
40
77
|
config;
|
|
41
78
|
/** sessionId → AgentStatus */
|
|
@@ -214,7 +251,7 @@ export class AgentEngine {
|
|
|
214
251
|
return { actualMessage: options.message, skillMatched: false };
|
|
215
252
|
}
|
|
216
253
|
/** 构建初始消息列表(system + history + user) */
|
|
217
|
-
buildInitialMessages(options, skillContent, actualMessage, skillMatched) {
|
|
254
|
+
async buildInitialMessages(options, skillContent, actualMessage, skillMatched) {
|
|
218
255
|
const { sessionId } = options;
|
|
219
256
|
let systemPrompt = this.config.basePrompt || '';
|
|
220
257
|
if (options.systemPromptOverride) {
|
|
@@ -257,8 +294,9 @@ export class AgentEngine {
|
|
|
257
294
|
systemPrompt += PROGRESS_NARRATION_RULES;
|
|
258
295
|
}
|
|
259
296
|
}
|
|
260
|
-
//
|
|
261
|
-
//
|
|
297
|
+
// 去重检测:如果历史最后一条已经是当前用户消息(由 buildThreadHistory 从 DB 恢复),
|
|
298
|
+
// 则在构建历史摘要时排除该消息,但仍需将当前用户消息作为 userMessage 传入。
|
|
299
|
+
// 之前的实现将 userMessage 设为 undefined,导致 LLM 收不到用户实际请求 → 空响应。
|
|
262
300
|
const history = this.historyManager.getHistory(sessionId);
|
|
263
301
|
const lastMsg = history.length > 0 ? history[history.length - 1] : null;
|
|
264
302
|
// 多模态消息去重:比较文本内容
|
|
@@ -266,17 +304,158 @@ export class AgentEngine {
|
|
|
266
304
|
const isDuplicateUserMessage = lastMsg?.role === 'user'
|
|
267
305
|
&& typeof lastMsg.content === 'string'
|
|
268
306
|
&& lastMsg.content === actualText;
|
|
307
|
+
if (isDuplicateUserMessage) {
|
|
308
|
+
console.log(`[Engine] Detected duplicate user message in history (likely restored from DB), excluding from summary and re-appending as userMessage`);
|
|
309
|
+
}
|
|
269
310
|
// 非破坏性上下文投影(Context Collapse):
|
|
270
311
|
// 当历史 token 超过预算时,使用投影而非破坏性压缩,保留原始历史完整
|
|
271
312
|
const maxContextTokens = this.config.contextBudget?.maxContextTokens ?? 100000;
|
|
272
313
|
const projectedHistory = this.historyManager.getProjectedHistory
|
|
273
314
|
? this.historyManager.getProjectedHistory(sessionId, maxContextTokens)
|
|
274
315
|
: history;
|
|
316
|
+
// 历史摘要 + XML 标签:将多轮对话历史压缩为 ≤300 字摘要,
|
|
317
|
+
// 用 <conversation_history> 标签包裹注入为 user+assistant 消息对。
|
|
318
|
+
// 摘要是陈述句,当前消息是指令句,语义上天然区分,从结构上消除混淆。
|
|
319
|
+
// 当 isDuplicateUserMessage 时,buildHistoryMessages 会从摘要中排除当前消息,
|
|
320
|
+
// 然后通过 userMessage 参数重新追加,确保 LLM 能看到用户的实际请求。
|
|
321
|
+
const historyMessages = isDuplicateUserMessage
|
|
322
|
+
? await this.buildHistoryMessages(projectedHistory, actualText)
|
|
323
|
+
: await this.buildHistoryMessages(projectedHistory, null);
|
|
275
324
|
return this.config.contextManager.buildMessages({
|
|
276
325
|
systemPrompt,
|
|
277
|
-
history:
|
|
278
|
-
userMessage:
|
|
326
|
+
history: historyMessages,
|
|
327
|
+
userMessage: actualMessage,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* 将对话历史压缩为摘要,用 <conversation_history> XML 标签包裹,
|
|
332
|
+
* 注入为 user 消息 + assistant 确认对。
|
|
333
|
+
*
|
|
334
|
+
* 摘要是陈述句,当前消息是指令句,语义上天然区分。
|
|
335
|
+
* ≤2 条消息时直接拼接,省去 LLM 调用;LLM 失败时回退到简单拼接。
|
|
336
|
+
*
|
|
337
|
+
* @param history 历史消息列表
|
|
338
|
+
* @param currentText 当前用户消息文本(用于排除最后一条匹配的 user 消息,null 表示全部纳入摘要)
|
|
339
|
+
* @returns 摘要消息对 [user: <conversation_history>..., assistant: 确认]
|
|
340
|
+
*/
|
|
341
|
+
async buildHistoryMessages(history, currentText) {
|
|
342
|
+
if (history.length === 0)
|
|
343
|
+
return [];
|
|
344
|
+
// 排除最后一条与当前消息匹配的 user 消息(避免重复)
|
|
345
|
+
let effectiveHistory = history;
|
|
346
|
+
if (currentText) {
|
|
347
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
348
|
+
if (history[i].role === 'user') {
|
|
349
|
+
const text = typeof history[i].content === 'string'
|
|
350
|
+
? history[i].content
|
|
351
|
+
: extractTextFromContent(history[i].content);
|
|
352
|
+
if (text === currentText) {
|
|
353
|
+
effectiveHistory = history.slice(0, i);
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (effectiveHistory.length === 0)
|
|
360
|
+
return [];
|
|
361
|
+
// 提取纯文本用于摘要(包含工具调用和工具结果,否则 LLM 丢失关键上下文)
|
|
362
|
+
const textHistory = effectiveHistory
|
|
363
|
+
.map(m => {
|
|
364
|
+
if (m.role === 'tool') {
|
|
365
|
+
// 工具结果消息:保留状态和摘要
|
|
366
|
+
const text = typeof m.content === 'string'
|
|
367
|
+
? m.content
|
|
368
|
+
: extractTextFromContent(m.content);
|
|
369
|
+
// 保留 artifact URL(关键!否则后续 send_email 无法引用已生成的报告)
|
|
370
|
+
const urlMatch = text.match(/(?:URL|url|artifact)[::]\s*(\/api\/[^\s"']+)/i);
|
|
371
|
+
const urlInfo = urlMatch ? ` [artifact_url: ${urlMatch[1]}]` : '';
|
|
372
|
+
const truncated = text.length > 150
|
|
373
|
+
? text.slice(0, 150) + '...' + urlInfo
|
|
374
|
+
: text + urlInfo;
|
|
375
|
+
return `[工具结果]: ${truncated}`;
|
|
376
|
+
}
|
|
377
|
+
const text = typeof m.content === 'string'
|
|
378
|
+
? m.content
|
|
379
|
+
: extractTextFromContent(m.content);
|
|
380
|
+
// 为 assistant 消息附加工具调用摘要(关键!否则 send_email 等参数完全丢失)
|
|
381
|
+
let toolInfo = '';
|
|
382
|
+
if (m.role === 'assistant' && m.tool_calls?.length) {
|
|
383
|
+
const callSummaries = m.tool_calls.map(tc => {
|
|
384
|
+
let args = {};
|
|
385
|
+
try {
|
|
386
|
+
args = JSON.parse(tc.function.arguments);
|
|
387
|
+
}
|
|
388
|
+
catch { /* ignore */ }
|
|
389
|
+
return summarizeToolCall(tc.function.name, args);
|
|
390
|
+
});
|
|
391
|
+
toolInfo = ` [调用: ${callSummaries.join('; ')}]`;
|
|
392
|
+
}
|
|
393
|
+
// 截断过长的内容(assistant 保留较短,因为工具调用信息更重要)
|
|
394
|
+
const maxLen = m.role === 'assistant' ? 150 : 200;
|
|
395
|
+
const truncated = text.length > maxLen
|
|
396
|
+
? text.slice(0, maxLen) + '...'
|
|
397
|
+
: text;
|
|
398
|
+
return `${m.role === 'user' ? '用户' : '助手'}: ${truncated}${toolInfo}`;
|
|
279
399
|
});
|
|
400
|
+
if (textHistory.length === 0)
|
|
401
|
+
return [];
|
|
402
|
+
// ≤2 条消息直接拼接,省去 LLM 调用
|
|
403
|
+
let summary;
|
|
404
|
+
if (textHistory.length <= 2) {
|
|
405
|
+
summary = textHistory.join('\n');
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
summary = await this.summarizeHistory(textHistory);
|
|
409
|
+
}
|
|
410
|
+
// 用 XML 标签包裹,注入为 user + assistant 确认对
|
|
411
|
+
return [
|
|
412
|
+
{
|
|
413
|
+
role: 'user',
|
|
414
|
+
content: `<conversation_history>\n${summary}\n</conversation_history>`,
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
role: 'assistant',
|
|
418
|
+
content: '好的,我已了解之前的对话内容。请告诉我您现在的需求。',
|
|
419
|
+
},
|
|
420
|
+
];
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* 通过轻量 LLM 调用将多轮对话历史压缩为 ≤800 字的陈述句摘要。
|
|
424
|
+
* LLM 失败时回退到简单拼接。
|
|
425
|
+
*/
|
|
426
|
+
async summarizeHistory(textHistory) {
|
|
427
|
+
try {
|
|
428
|
+
const provider = this.config.llmProvider;
|
|
429
|
+
const prompt = `请将以下对话历史压缩为不超过800字的陈述句摘要。必须保留:
|
|
430
|
+
1. 用户的核心请求和意图
|
|
431
|
+
2. 助手调用了哪些工具(工具名+关键参数,如收件人邮箱、邮件主题等)
|
|
432
|
+
3. 工具调用的结果(成功/失败)
|
|
433
|
+
4. 已生成的内容摘要(如报告主题、数据要点)
|
|
434
|
+
5. **artifact URL**(格式如 /api/work/artifacts/...),这是后续发送邮件等操作的关键引用
|
|
435
|
+
|
|
436
|
+
不要添加任何评价或建议,只做客观压缩:
|
|
437
|
+
|
|
438
|
+
${textHistory.join('\n')}`;
|
|
439
|
+
const response = await provider.chat({
|
|
440
|
+
messages: [
|
|
441
|
+
{ role: 'system', content: '你是一个对话摘要助手。将对话历史压缩为简洁的陈述句摘要,不超过800字。务必保留工具调用名称和关键参数(如邮箱地址、邮件主题等),因为这些是后续操作的重要上下文。' },
|
|
442
|
+
{ role: 'user', content: prompt },
|
|
443
|
+
],
|
|
444
|
+
temperature: 0.3,
|
|
445
|
+
maxTokens: 1000,
|
|
446
|
+
});
|
|
447
|
+
const rawContent = response.choices?.[0]?.message?.content;
|
|
448
|
+
const summary = typeof rawContent === 'string' ? rawContent.trim() : '';
|
|
449
|
+
if (summary && summary.length > 0) {
|
|
450
|
+
return summary.length > 800 ? summary.slice(0, 800) + '...' : summary;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
catch (e) {
|
|
454
|
+
console.warn('[Engine] summarizeHistory failed, falling back to concatenation:', e?.message || e);
|
|
455
|
+
}
|
|
456
|
+
// 回退:简单拼接,截断到 800 字
|
|
457
|
+
const fallback = textHistory.join('\n');
|
|
458
|
+
return fallback.length > 800 ? fallback.slice(0, 800) + '...' : fallback;
|
|
280
459
|
}
|
|
281
460
|
// ---- 工具定义 ----
|
|
282
461
|
/** 获取工具定义列表(含预过滤 + 渐进式发现) */
|
|
@@ -342,7 +521,7 @@ export class AgentEngine {
|
|
|
342
521
|
console.log(`[Engine] session=${options.sessionId} tools(${toolDefs.length}): ${toolDefs.map(t => t.function.name).join(', ') || '(none)'}`);
|
|
343
522
|
return {
|
|
344
523
|
sessionId: options.sessionId,
|
|
345
|
-
messages: this.buildInitialMessages(options, skillContent, actualMessage, skillMatched),
|
|
524
|
+
messages: await this.buildInitialMessages(options, skillContent, actualMessage, skillMatched),
|
|
346
525
|
toolDefs,
|
|
347
526
|
toolContext: this.buildToolContext(options, internalAbort),
|
|
348
527
|
internalAbort,
|
|
@@ -363,7 +542,6 @@ export class AgentEngine {
|
|
|
363
542
|
async *agentLoop(state) {
|
|
364
543
|
while (state.iteration < this.config.maxIterations) {
|
|
365
544
|
state.iteration++;
|
|
366
|
-
state.lastIterationHadToolCalls = false; // 每轮迭代重置,工具执行后才置 true
|
|
367
545
|
// 中断检查
|
|
368
546
|
if (state.internalAbort.signal.aborted) {
|
|
369
547
|
yield this.emitDone(state);
|
|
@@ -422,13 +600,29 @@ export class AgentEngine {
|
|
|
422
600
|
state.totalUsage.totalTokens += usage.totalTokens || 0;
|
|
423
601
|
}
|
|
424
602
|
if (!assistant) {
|
|
425
|
-
// LLM
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
603
|
+
// 所有 LLM provider 均已失败(callWithRetry 内部已重试+降级),
|
|
604
|
+
// 不再发 done 事件(callWithRetry 已发出带详细错误信息的 error 事件),
|
|
605
|
+
// 仅完成清理,避免 done 事件覆盖前端已展示的错误消息
|
|
606
|
+
console.warn(`[Engine] All LLM providers exhausted at iteration ${state.iteration}, stopping agent loop`);
|
|
607
|
+
this.statusMap.set(state.sessionId, 'idle');
|
|
608
|
+
// 记录 Token 用量到 UsageTracker
|
|
609
|
+
if (this.config.usageTracker && state.totalUsage) {
|
|
610
|
+
const modelInfo = this.config.llmProvider.getModel();
|
|
611
|
+
this.config.usageTracker.record({
|
|
612
|
+
sessionId: state.sessionId,
|
|
613
|
+
provider: modelInfo.provider,
|
|
614
|
+
model: modelInfo.model,
|
|
615
|
+
usage: state.totalUsage,
|
|
616
|
+
iterations: state.iteration,
|
|
617
|
+
});
|
|
430
618
|
}
|
|
431
|
-
|
|
619
|
+
this.runHooks('on_complete', {
|
|
620
|
+
sessionId: state.sessionId,
|
|
621
|
+
iteration: state.iteration,
|
|
622
|
+
totalIterations: state.iteration,
|
|
623
|
+
usage: state.totalUsage,
|
|
624
|
+
}).catch(() => { });
|
|
625
|
+
return;
|
|
432
626
|
}
|
|
433
627
|
// Hook: after_ll_call
|
|
434
628
|
await this.runHooks('after_ll_call', {
|
|
@@ -454,6 +648,7 @@ export class AgentEngine {
|
|
|
454
648
|
if (!assistant.tool_calls?.length) {
|
|
455
649
|
if (state.lastIterationHadToolCalls && state.continuationNudges < 2) {
|
|
456
650
|
state.continuationNudges++;
|
|
651
|
+
state.lastIterationHadToolCalls = false; // 已消费标记,避免重复注入
|
|
457
652
|
console.log(`[Engine] LLM stopped after tool calls without new tool_calls, injecting continuation nudge #${state.continuationNudges} at iteration ${state.iteration}`);
|
|
458
653
|
this.injectContinuationNudge(state);
|
|
459
654
|
// 不退出,继续下一轮迭代让 LLM 继续执行任务
|