@zhin.js/agent 0.1.30 → 0.1.31
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 +47 -22
- package/lib/builtin/run-deferred-task-tool.d.ts +1 -1
- package/lib/builtin/run-deferred-task-tool.d.ts.map +1 -1
- package/lib/builtin/run-deferred-task-tool.js +1 -1
- package/lib/builtin/run-deferred-task-tool.js.map +1 -1
- package/lib/config/normalize-ai-config.d.ts +4 -0
- package/lib/config/normalize-ai-config.d.ts.map +1 -1
- package/lib/config/normalize-ai-config.js +23 -6
- package/lib/config/normalize-ai-config.js.map +1 -1
- package/lib/config/provider-instance.d.ts.map +1 -1
- package/lib/config/provider-instance.js +26 -7
- package/lib/config/provider-instance.js.map +1 -1
- package/lib/config/types.d.ts +2 -1
- package/lib/config/types.d.ts.map +1 -1
- package/lib/config/types.js.map +1 -1
- package/lib/config/validate-ai-config.d.ts.map +1 -1
- package/lib/config/validate-ai-config.js +5 -0
- package/lib/config/validate-ai-config.js.map +1 -1
- package/lib/deferred-worker-runner.d.ts +2 -0
- package/lib/deferred-worker-runner.d.ts.map +1 -1
- package/lib/deferred-worker-runner.js +17 -34
- package/lib/deferred-worker-runner.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/init/activate-ai-database-storage.d.ts +1 -1
- package/lib/init/activate-ai-database-storage.d.ts.map +1 -1
- package/lib/init/activate-ai-database-storage.js +16 -13
- package/lib/init/activate-ai-database-storage.js.map +1 -1
- package/lib/init/create-zhin-agent.d.ts.map +1 -1
- package/lib/init/create-zhin-agent.js +17 -2
- package/lib/init/create-zhin-agent.js.map +1 -1
- package/lib/init/register-db-models.d.ts.map +1 -1
- package/lib/init/register-db-models.js +7 -8
- package/lib/init/register-db-models.js.map +1 -1
- package/lib/init/register-db-upgrade.d.ts +1 -1
- package/lib/init/register-db-upgrade.js +2 -2
- package/lib/init/register-db-upgrade.js.map +1 -1
- package/lib/init/register-management-tools.js +1 -1
- package/lib/init/register-management-tools.js.map +1 -1
- package/lib/media/subagent-user-delivery.d.ts +2 -9
- package/lib/media/subagent-user-delivery.d.ts.map +1 -1
- package/lib/media/subagent-user-delivery.js.map +1 -1
- package/lib/service.d.ts +31 -22
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +48 -19
- package/lib/service.js.map +1 -1
- package/lib/subagent-ai-events.d.ts +2 -2
- package/lib/subagent-ai-events.d.ts.map +1 -1
- package/lib/subagent-ai-events.js +17 -21
- package/lib/subagent-ai-events.js.map +1 -1
- package/lib/subagent.d.ts.map +1 -1
- package/lib/subagent.js +44 -59
- package/lib/subagent.js.map +1 -1
- package/lib/zhin-agent/agent-loop-standalone.d.ts +32 -0
- package/lib/zhin-agent/agent-loop-standalone.d.ts.map +1 -0
- package/lib/zhin-agent/agent-loop-standalone.js +144 -0
- package/lib/zhin-agent/agent-loop-standalone.js.map +1 -0
- package/lib/zhin-agent/agent-loop-turn.d.ts +51 -0
- package/lib/zhin-agent/agent-loop-turn.d.ts.map +1 -0
- package/lib/zhin-agent/agent-loop-turn.js +287 -0
- package/lib/zhin-agent/agent-loop-turn.js.map +1 -0
- package/lib/zhin-agent/builtin-tools.d.ts +3 -2
- package/lib/zhin-agent/builtin-tools.d.ts.map +1 -1
- package/lib/zhin-agent/builtin-tools.js +15 -16
- package/lib/zhin-agent/builtin-tools.js.map +1 -1
- package/lib/zhin-agent/config.d.ts +5 -2
- package/lib/zhin-agent/config.d.ts.map +1 -1
- package/lib/zhin-agent/config.js +3 -2
- package/lib/zhin-agent/config.js.map +1 -1
- package/lib/zhin-agent/deferred-delivery.d.ts +5 -0
- package/lib/zhin-agent/deferred-delivery.d.ts.map +1 -0
- package/lib/zhin-agent/deferred-delivery.js +29 -0
- package/lib/zhin-agent/deferred-delivery.js.map +1 -0
- package/lib/zhin-agent/index.d.ts +40 -8
- package/lib/zhin-agent/index.d.ts.map +1 -1
- package/lib/zhin-agent/index.js +162 -17
- package/lib/zhin-agent/index.js.map +1 -1
- package/lib/zhin-agent/model-resolver.d.ts +0 -1
- package/lib/zhin-agent/model-resolver.d.ts.map +1 -1
- package/lib/zhin-agent/model-resolver.js.map +1 -1
- package/lib/zhin-agent/multimodal-message.d.ts +9 -0
- package/lib/zhin-agent/multimodal-message.d.ts.map +1 -0
- package/lib/zhin-agent/multimodal-message.js +69 -0
- package/lib/zhin-agent/multimodal-message.js.map +1 -0
- package/lib/zhin-agent/prompt-access.d.ts +4 -0
- package/lib/zhin-agent/prompt-access.d.ts.map +1 -0
- package/lib/zhin-agent/prompt-access.js +7 -0
- package/lib/zhin-agent/prompt-access.js.map +1 -0
- package/lib/zhin-agent/prompt-controller.d.ts +53 -0
- package/lib/zhin-agent/prompt-controller.d.ts.map +1 -0
- package/lib/zhin-agent/prompt-controller.js +165 -0
- package/lib/zhin-agent/prompt-controller.js.map +1 -0
- package/lib/zhin-agent/prompt-input.d.ts +3 -0
- package/lib/zhin-agent/prompt-input.d.ts.map +1 -0
- package/lib/zhin-agent/prompt-input.js +8 -0
- package/lib/zhin-agent/prompt-input.js.map +1 -0
- package/lib/zhin-agent/session-io.d.ts +12 -7
- package/lib/zhin-agent/session-io.d.ts.map +1 -1
- package/lib/zhin-agent/session-io.js +18 -15
- package/lib/zhin-agent/session-io.js.map +1 -1
- package/lib/zhin-agent/session-message-queue.d.ts +20 -0
- package/lib/zhin-agent/session-message-queue.d.ts.map +1 -0
- package/lib/zhin-agent/session-message-queue.js +52 -0
- package/lib/zhin-agent/session-message-queue.js.map +1 -0
- package/lib/zhin-agent/tool-calls-user-format.d.ts.map +1 -1
- package/lib/zhin-agent/tool-calls-user-format.js +3 -0
- package/lib/zhin-agent/tool-calls-user-format.js.map +1 -1
- package/lib/zhin-agent/tool-orchestration.d.ts.map +1 -1
- package/lib/zhin-agent/tool-orchestration.js +35 -5
- package/lib/zhin-agent/tool-orchestration.js.map +1 -1
- package/lib/zhin-agent/tool-runtime.d.ts +2 -2
- package/lib/zhin-agent/tool-runtime.d.ts.map +1 -1
- package/lib/zhin-agent/tool-runtime.js +4 -4
- package/lib/zhin-agent/tool-runtime.js.map +1 -1
- package/lib/zhin-agent/turn-context.d.ts +9 -0
- package/lib/zhin-agent/turn-context.d.ts.map +1 -0
- package/lib/zhin-agent/turn-context.js +12 -0
- package/lib/zhin-agent/turn-context.js.map +1 -0
- package/lib/zhin-agent/turn-pipeline.d.ts +5 -1
- package/lib/zhin-agent/turn-pipeline.d.ts.map +1 -1
- package/lib/zhin-agent/turn-pipeline.js +113 -436
- package/lib/zhin-agent/turn-pipeline.js.map +1 -1
- package/lib/zhin-agent/zhin-agent-private.d.ts +11 -4
- package/lib/zhin-agent/zhin-agent-private.d.ts.map +1 -1
- package/lib/zhin-agent/zhin-agent-private.js.map +1 -1
- package/package.json +3 -3
- package/lib/zhin-agent/llm-runner.d.ts +0 -18
- package/lib/zhin-agent/llm-runner.d.ts.map +0 -1
- package/lib/zhin-agent/llm-runner.js +0 -163
- package/lib/zhin-agent/llm-runner.js.map +0 -1
|
@@ -1,37 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { createAgent } from '@zhin.js/ai';
|
|
1
|
+
import { formatCompact, Logger } from '@zhin.js/logger';
|
|
2
|
+
import { createUserMessage } from '@zhin.js/ai';
|
|
4
3
|
import { resolveIMSessionIdFromToolContext } from '@zhin.js/ai';
|
|
5
4
|
import { parseOutput } from '@zhin.js/ai';
|
|
6
5
|
import { detectTone } from '@zhin.js/ai';
|
|
7
6
|
import { ensureMcpConnectionsForBinding, getMcpToolsForBinding, } from '../orchestrator/mcp-lifecycle.js';
|
|
8
|
-
import { applyExecPolicyToTools } from '../security/exec-policy.js';
|
|
9
|
-
import { runWithBashToolContext } from '../security/bash-tool-context.js';
|
|
10
7
|
import { triggerAIHook, createAIHookEvent } from '../hooks.js';
|
|
11
8
|
import { resolveModelCandidates } from './model-resolver.js';
|
|
12
|
-
import { streamChatWithHistory, streamChatWithVisionMedia } from './llm-runner.js';
|
|
13
|
-
import { INBOUND_MEDIA_PARTS_EXTRA_KEY } from '../media/constants.js';
|
|
14
9
|
import { mergeToolOutboundElements } from '../media/media-tool-bridge.js';
|
|
15
10
|
import { providerSupportsVision } from '../media/vision-capability.js';
|
|
16
|
-
import {
|
|
11
|
+
import { touchSession, formatUserContentForSession, resolveSessionIsNewBeforeCreate, beginTurnSession as beginTurnSessionIO, } from './session-io.js';
|
|
12
|
+
import { runAgentLoopTextTurn, runAgentLoopVisionTurn } from './agent-loop-turn.js';
|
|
17
13
|
import { buildEnhancedPersona } from './prompt.js';
|
|
18
|
-
import { collectRuntimeTools
|
|
19
|
-
import { sanitizeAssistantReply, stripThinkBlocks } from './text-sanitize.js';
|
|
20
|
-
import { pruneHistoryWithBudget } from './context-budget.js';
|
|
14
|
+
import { collectRuntimeTools } from './tool-runtime.js';
|
|
21
15
|
import { buildMultimodalVisionSystemPrompt } from './prompt-assembly.js';
|
|
22
|
-
import { formatToolCallsForUser } from './tool-calls-user-format.js';
|
|
23
|
-
import { resolveModelHarness } from './model-harness.js';
|
|
24
|
-
import { RESERVED_TOOL_NAMES, RESERVED_TOOL_NAME_PREFIXES } from '../reserved-tools.js';
|
|
25
|
-
import { createOwnerOrchestratedToolResultTransform } from '../orchestrator/owner-confirm-orchestration.js';
|
|
26
|
-
import { logPhase, usageLogFields } from './phase-trace.js';
|
|
27
16
|
import { attachWebSearchLocale } from './web-search-locale-attach.js';
|
|
28
17
|
import { EMPTY_USAGE } from './turn-metrics.js';
|
|
29
18
|
import { resolveAgentToolsForTurn } from './tool-orchestration.js';
|
|
30
|
-
import {
|
|
19
|
+
import { logPhase } from './phase-trace.js';
|
|
20
|
+
import { buildVisionUserMessage, summarizeMultimodalParts } from './multimodal-message.js';
|
|
21
|
+
import { DEFAULT_MULTIMODAL_CONFIG } from '../media/media-types.js';
|
|
31
22
|
const logger = new Logger(null, 'ZhinAgent');
|
|
32
23
|
const now = () => performance.now();
|
|
33
24
|
function sessionDeps(host) {
|
|
34
|
-
return {
|
|
25
|
+
return {
|
|
26
|
+
imSessionStore: host.imSessionStore,
|
|
27
|
+
agentSessionStore: host.agentSessionStore,
|
|
28
|
+
contextRepository: host.contextRepository,
|
|
29
|
+
};
|
|
35
30
|
}
|
|
36
31
|
async function beginTurnSession(host, context) {
|
|
37
32
|
const sessionKey = resolveIMSessionIdFromToolContext({
|
|
@@ -41,22 +36,21 @@ async function beginTurnSession(host, context) {
|
|
|
41
36
|
sceneId: context.sceneId,
|
|
42
37
|
senderId: context.senderId,
|
|
43
38
|
});
|
|
44
|
-
|
|
45
|
-
return { sessionKey, sessionId: imSession.session_id };
|
|
39
|
+
return beginTurnSessionIO(sessionDeps(host), sessionKey, context);
|
|
46
40
|
}
|
|
47
|
-
export async function processTextTurn(host, content, context, externalTools = [], onChunk) {
|
|
41
|
+
export async function processTextTurn(host, content, context, externalTools = [], onChunk, extras) {
|
|
48
42
|
const t0 = now();
|
|
49
|
-
const { senderId,
|
|
43
|
+
const { senderId, platform, botId } = context;
|
|
50
44
|
const sessionUserContent = formatUserContentForSession(context, content);
|
|
51
45
|
const userId = senderId || 'unknown';
|
|
52
46
|
const sessionKey = resolveIMSessionIdFromToolContext({
|
|
53
47
|
platform,
|
|
54
48
|
botId,
|
|
55
49
|
scope: context.scope,
|
|
56
|
-
sceneId,
|
|
50
|
+
sceneId: context.sceneId,
|
|
57
51
|
senderId,
|
|
58
52
|
});
|
|
59
|
-
const isNewSession = await resolveSessionIsNewBeforeCreate(sessionDeps(host), sessionKey
|
|
53
|
+
const isNewSession = await resolveSessionIsNewBeforeCreate(sessionDeps(host), sessionKey);
|
|
60
54
|
await host.waitForMemoryPersistence();
|
|
61
55
|
const { sessionId } = await beginTurnSession(host, context);
|
|
62
56
|
await host.emitter.dispatch('ai.processing.start', host.emitter.createPayload(sessionId, context, 'text', {
|
|
@@ -66,7 +60,6 @@ export async function processTextTurn(host, content, context, externalTools = []
|
|
|
66
60
|
mode: 'text',
|
|
67
61
|
provider: host.getTurnProvider().name,
|
|
68
62
|
});
|
|
69
|
-
// 0. Rate limit
|
|
70
63
|
const rateCheck = host.rateLimiter.check(userId);
|
|
71
64
|
if (!rateCheck.allowed) {
|
|
72
65
|
logPhase(host.phaseConfig, 'turn.rate_limited', sessionId, { userId });
|
|
@@ -79,12 +72,10 @@ export async function processTextTurn(host, content, context, externalTools = []
|
|
|
79
72
|
await host.finalizeActiveTurn({ usage: EMPTY_USAGE, path: 'rate_limited' });
|
|
80
73
|
return parseOutput(rateCheck.message || '请稍后再试');
|
|
81
74
|
}
|
|
82
|
-
// 0.1 发射 typing 事件,由适配器插件自行决定如何响应
|
|
83
75
|
host.emitter.emit('ai.typing.start', host.emitter.createPayload(sessionId, context, 'text', {
|
|
84
76
|
reason: 'processing',
|
|
85
77
|
}));
|
|
86
78
|
host.beginActiveTurn();
|
|
87
|
-
// 0.5 工具上下文:web_search 语言(档案 preferred_language / language,否则默认中文)
|
|
88
79
|
const contextForTools = await attachWebSearchLocale(context, userId, host.userProfiles);
|
|
89
80
|
triggerAIHook(createAIHookEvent('message', 'received', sessionId, {
|
|
90
81
|
userId,
|
|
@@ -93,7 +84,6 @@ export async function processTextTurn(host, content, context, externalTools = []
|
|
|
93
84
|
})).catch(() => { });
|
|
94
85
|
const turnBinding = host.activeBinding;
|
|
95
86
|
const mcpServerNames = turnBinding?.mcpServers ?? [];
|
|
96
|
-
// 0.9 Lazy-connect MCP servers listed on active agent binding
|
|
97
87
|
if (host.orchestrator && mcpServerNames.length > 0) {
|
|
98
88
|
await ensureMcpConnectionsForBinding(host.orchestrator.mcps, mcpServerNames, (event) => {
|
|
99
89
|
const payload = host.emitter.createPayload(sessionId, contextForTools, 'text', {
|
|
@@ -114,12 +104,11 @@ export async function processTextTurn(host, content, context, externalTools = []
|
|
|
114
104
|
}
|
|
115
105
|
});
|
|
116
106
|
}
|
|
117
|
-
// 1. Collect tools
|
|
118
107
|
const tFilter = now();
|
|
119
108
|
const mcpTools = host.orchestrator && mcpServerNames.length > 0
|
|
120
109
|
? getMcpToolsForBinding(host.orchestrator.mcps, mcpServerNames)
|
|
121
110
|
: [];
|
|
122
|
-
|
|
111
|
+
const allTools = collectRuntimeTools({
|
|
123
112
|
content,
|
|
124
113
|
context: contextForTools,
|
|
125
114
|
externalTools,
|
|
@@ -128,7 +117,7 @@ export async function processTextTurn(host, content, context, externalTools = []
|
|
|
128
117
|
externalRegistered: host.externalTools,
|
|
129
118
|
sessionId,
|
|
130
119
|
userId,
|
|
131
|
-
|
|
120
|
+
imTranscriptStore: host.imTranscriptStore,
|
|
132
121
|
userProfiles: host.userProfiles,
|
|
133
122
|
mcpTools,
|
|
134
123
|
});
|
|
@@ -141,323 +130,91 @@ export async function processTextTurn(host, content, context, externalTools = []
|
|
|
141
130
|
tool_search: true,
|
|
142
131
|
names: resolvedTools.map(t => t.name).join(',') || '(none)',
|
|
143
132
|
}));
|
|
144
|
-
|
|
145
|
-
const tMem = now();
|
|
146
|
-
const [rawHistoryMessages, profileSummary] = await Promise.all([
|
|
147
|
-
buildHistoryMessages(sessionDeps(host), sessionId, context, sessionUserContent),
|
|
148
|
-
host.userProfiles.buildProfileSummary(userId),
|
|
149
|
-
]);
|
|
150
|
-
const chatCandidates = resolveModelCandidates(host.getTurnProvider().models, host.modelRegistry, host.getTurnProvider().name, host.config, 'chat');
|
|
151
|
-
const { messages: historyMessages, result: pruneResult, budget: contextBudget, } = pruneHistoryWithBudget({
|
|
152
|
-
messages: rawHistoryMessages,
|
|
153
|
-
config: host.config,
|
|
154
|
-
provider: host.getTurnProvider(),
|
|
155
|
-
modelRegistry: host.modelRegistry,
|
|
156
|
-
model: chatCandidates[0],
|
|
157
|
-
});
|
|
158
|
-
if (pruneResult.droppedCount > 0) {
|
|
159
|
-
logger.debug(`[上下文窗口] 丢弃 ${pruneResult.droppedCount} 条历史消息 (${pruneResult.droppedTokens} tokens)`);
|
|
160
|
-
}
|
|
161
|
-
const memMs = (now() - tMem).toFixed(0);
|
|
162
|
-
logPhase(host.phaseConfig, 'context.ready', sessionId, { historyCount: historyMessages.length });
|
|
163
|
-
// 2.5 Tone + persona
|
|
133
|
+
const profileSummary = await host.userProfiles.buildProfileSummary(userId);
|
|
164
134
|
const toneHint = host.config.toneAwareness ? detectTone(content).hint : '';
|
|
165
135
|
const personaEnhanced = buildEnhancedPersona(host.config, profileSummary, toneHint);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const chatResult = inboundVision?.length
|
|
180
|
-
? await streamChatWithVisionMedia({ provider: host.getTurnProvider(), modelRegistry: host.modelRegistry, config: host.config }, sessionUserContent, inboundVision, chatSystemPrompt, historyMessages, onChunk, liteModel || host.config.visionModel)
|
|
181
|
-
: await streamChatWithHistory({ provider: host.getTurnProvider(), modelRegistry: host.modelRegistry, config: host.config }, sessionUserContent, chatSystemPrompt, historyMessages, onChunk, liteModel);
|
|
182
|
-
let reply = sanitizeAssistantReply(chatResult.content);
|
|
183
|
-
await host.emitter.dispatch('ai.response', host.emitter.createPayload(sessionId, context, 'text', {
|
|
184
|
-
path: 'chat',
|
|
185
|
-
model: chatResult.model,
|
|
186
|
-
reply,
|
|
187
|
-
}));
|
|
188
|
-
const llmMs = (now() - tLLM).toFixed(0);
|
|
189
|
-
logPhase(host.phaseConfig, 'chat.llm.end', sessionId, {
|
|
190
|
-
durationMs: Number(llmMs),
|
|
191
|
-
...usageLogFields(chatResult.usage ?? undefined),
|
|
192
|
-
});
|
|
193
|
-
logger.debug(formatCompact({
|
|
194
|
-
mode: 'chat',
|
|
195
|
-
filter_ms: filterMs,
|
|
196
|
-
mem_ms: memMs,
|
|
197
|
-
llm_ms: llmMs,
|
|
198
|
-
total_ms: Math.round(now() - t0),
|
|
199
|
-
}));
|
|
200
|
-
await touchSession(sessionDeps(host), sessionId);
|
|
201
|
-
if (isNewSession) {
|
|
202
|
-
host.emitSessionNewEvent(sessionId, context, 'text', sessionUserContent, reply);
|
|
203
|
-
}
|
|
204
|
-
await host.finalizeActiveTurn({
|
|
205
|
-
usage: chatResult.usage ?? EMPTY_USAGE,
|
|
206
|
-
path: 'chat',
|
|
207
|
-
model: chatResult.model,
|
|
208
|
-
});
|
|
209
|
-
await host.emitter.dispatch('ai.processing.finish', host.emitter.createPayload(sessionId, context, 'text', {
|
|
210
|
-
path: 'chat',
|
|
211
|
-
model: chatResult.model,
|
|
212
|
-
reply,
|
|
213
|
-
}));
|
|
214
|
-
host.emitter.emit('ai.typing.stop', host.emitter.createPayload(sessionId, context, 'text', {
|
|
215
|
-
reason: 'processing_complete',
|
|
216
|
-
}));
|
|
217
|
-
logPhase(host.phaseConfig, 'turn.end', sessionId, { path: 'chat' });
|
|
218
|
-
return parseOutput(reply);
|
|
219
|
-
}
|
|
220
|
-
logger.debug(`[工具路径] 过滤=${filterMs}ms, 记忆=${memMs}ms, ${allTools.length} 工具 (${allTools.map(t => t.name).join(', ')})`);
|
|
221
|
-
// 4. Pre-executable tools
|
|
222
|
-
const preExecCandidates = allTools.filter(tool => tool.preExecutable);
|
|
223
|
-
const tPre = preExecCandidates.length > 0 ? now() : 0;
|
|
224
|
-
if (preExecCandidates.length > 0) {
|
|
225
|
-
logger.debug(`预执行: ${preExecCandidates.map(t => t.name).join(', ')}`);
|
|
226
|
-
}
|
|
227
|
-
const toolRun = await planToolRun(resolvedTools, host.config.preExecTimeout);
|
|
228
|
-
logPhase(host.phaseConfig, 'preexec.done', sessionId, {
|
|
229
|
-
mode: toolRun.mode,
|
|
230
|
-
preExecutedTools: toolRun.preExecution.tools.length,
|
|
231
|
-
});
|
|
232
|
-
const preData = toolRun.preExecution.data;
|
|
233
|
-
if (tPre > 0) {
|
|
234
|
-
logger.debug(`预执行耗时: ${(now() - tPre).toFixed(0)}ms`);
|
|
235
|
-
}
|
|
236
|
-
// 6. Path selection
|
|
237
|
-
let reply;
|
|
238
|
-
let agentToolCalls = [];
|
|
239
|
-
if (toolRun.mode === 'pre-exec-fast-path') {
|
|
240
|
-
logPhase(host.phaseConfig, 'path.pre_exec_fast', sessionId, { toolCount: allTools.length });
|
|
241
|
-
// Fast path
|
|
242
|
-
const tLLM = now();
|
|
243
|
-
const prompt = buildFastPathSystemPrompt(host, personaEnhanced, preData, contextForTools);
|
|
244
|
-
logger.debug(formatCompact({ mode: 'fast', prompt_chars: prompt.length }));
|
|
245
|
-
logPhase(host.phaseConfig, 'fast.llm.start', sessionId, { model: chatCandidates[0] || '' });
|
|
246
|
-
const fastResult = await streamChatWithHistory({ provider: host.getTurnProvider(), modelRegistry: host.modelRegistry, config: host.config }, sessionUserContent, prompt, historyMessages, onChunk);
|
|
247
|
-
reply = sanitizeAssistantReply(fastResult.content);
|
|
248
|
-
logPhase(host.phaseConfig, 'fast.llm.end', sessionId, {
|
|
249
|
-
durationMs: Math.round(now() - tLLM),
|
|
250
|
-
...usageLogFields(fastResult.usage ?? undefined),
|
|
251
|
-
});
|
|
252
|
-
logger.debug(`[快速路径] 过滤=${filterMs}ms, 记忆=${memMs}ms, LLM=${(now() - tLLM).toFixed(0)}ms, 总=${(now() - t0).toFixed(0)}ms`);
|
|
253
|
-
await host.finalizeActiveTurn({
|
|
254
|
-
usage: fastResult.usage ?? EMPTY_USAGE,
|
|
255
|
-
path: 'fast',
|
|
256
|
-
model: fastResult.model,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
logPhase(host.phaseConfig, 'path.agent', sessionId, { toolCount: allTools.length });
|
|
261
|
-
const tAgent = now();
|
|
262
|
-
logger.debug(`Agent 路径: ${allTools.length} 个工具`);
|
|
263
|
-
const systemPrompt = await buildAgentPathSystemPrompt(host, {
|
|
264
|
-
content,
|
|
265
|
-
context: contextForTools,
|
|
136
|
+
const chatCandidates = resolveModelCandidates(host.getTurnProvider().models, host.modelRegistry, host.getTurnProvider().name, host.config, 'chat');
|
|
137
|
+
logPhase(host.phaseConfig, 'path.agent_loop', sessionId, { toolCount: allTools.length });
|
|
138
|
+
const userMessages = extras?.prebuiltMessages?.length
|
|
139
|
+
? extras.prebuiltMessages
|
|
140
|
+
: [createUserMessage(sessionUserContent)];
|
|
141
|
+
const loopResult = await host.promptController.schedule({
|
|
142
|
+
sessionKey,
|
|
143
|
+
sessionId,
|
|
144
|
+
userMessages,
|
|
145
|
+
context,
|
|
146
|
+
onChunk,
|
|
147
|
+
execute: (initialMessages, hooks, signal, _turnId) => runAgentLoopTextTurn({
|
|
148
|
+
host,
|
|
266
149
|
sessionId,
|
|
150
|
+
sessionUserContent,
|
|
151
|
+
rawContent: content,
|
|
152
|
+
context,
|
|
153
|
+
contextForTools,
|
|
154
|
+
allTools,
|
|
155
|
+
resolvedTools,
|
|
267
156
|
personaEnhanced,
|
|
268
|
-
|
|
157
|
+
modelId: chatCandidates[0] || host.getTurnProvider().models[0] || 'gpt-4o-mini',
|
|
158
|
+
modelCandidates: chatCandidates,
|
|
159
|
+
onChunk,
|
|
160
|
+
initialMessages,
|
|
161
|
+
promptHooks: hooks,
|
|
162
|
+
signal,
|
|
269
163
|
deferredStats,
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const harness = resolveModelHarness(host.getTurnProvider().name, chatCandidates[0] || '', host.config.modelHarness);
|
|
280
|
-
const baseIterations = harness.maxIterations ?? host.config.maxIterations;
|
|
281
|
-
const effectiveMaxIterations = hasSkillActivation
|
|
282
|
-
? baseIterations + SKILL_ITERATION_BOOST
|
|
283
|
-
: baseIterations;
|
|
284
|
-
logPhase(host.phaseConfig, 'harness.resolved', sessionId, {
|
|
285
|
-
model: chatCandidates[0] || '',
|
|
286
|
-
harnessMaxIterations: harness.maxIterations ?? null,
|
|
287
|
-
effectiveMaxIterations,
|
|
288
|
-
});
|
|
289
|
-
let orchestrationPlugin = host.emitter.getHostPlugin() ?? undefined;
|
|
290
|
-
if (!orchestrationPlugin) {
|
|
291
|
-
try {
|
|
292
|
-
orchestrationPlugin = getPlugin().root ?? getPlugin();
|
|
293
|
-
}
|
|
294
|
-
catch {
|
|
295
|
-
logger.warn(formatCompact({ warn: 'no_host_plugin' }));
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
const llmAgent = createAgent(host.getTurnProvider(), {
|
|
299
|
-
model: chatCandidates[0],
|
|
300
|
-
modelFallbacks: chatCandidates.slice(1),
|
|
301
|
-
systemPrompt,
|
|
302
|
-
tools: agentTools,
|
|
303
|
-
maxIterations: effectiveMaxIterations,
|
|
304
|
-
turnTimeout: host.config.timeout,
|
|
305
|
-
contextWindow: contextBudget.contextWindow,
|
|
306
|
-
reservedToolNames: RESERVED_TOOL_NAMES,
|
|
307
|
-
reservedToolNamePrefixes: RESERVED_TOOL_NAME_PREFIXES,
|
|
308
|
-
transformToolResult: createOwnerOrchestratedToolResultTransform({
|
|
309
|
-
toolContext: contextForTools,
|
|
310
|
-
disableHardOrchestration: false,
|
|
311
|
-
plugin: orchestrationPlugin,
|
|
312
|
-
}),
|
|
313
|
-
policyDenialStopAfter: host.config.policyDenialStopAfter,
|
|
314
|
-
});
|
|
315
|
-
llmAgent.on('thinking', (message) => {
|
|
316
|
-
host.emitter.emit('ai.thinking', host.emitter.createPayload(sessionId, contextForTools, 'text', {
|
|
317
|
-
path: 'agent',
|
|
318
|
-
thinking: message,
|
|
319
|
-
}));
|
|
320
|
-
});
|
|
321
|
-
llmAgent.on('tool_call', (toolName, args) => {
|
|
322
|
-
host.emitter.emit('ai.tool.call', host.emitter.createPayload(sessionId, contextForTools, 'text', {
|
|
323
|
-
path: 'agent',
|
|
324
|
-
toolName,
|
|
325
|
-
args,
|
|
326
|
-
}));
|
|
327
|
-
});
|
|
328
|
-
llmAgent.on('tool_result', (toolName, result) => {
|
|
329
|
-
host.emitter.emit('ai.tool.result', host.emitter.createPayload(sessionId, contextForTools, 'text', {
|
|
330
|
-
path: 'agent',
|
|
331
|
-
toolName,
|
|
332
|
-
result,
|
|
333
|
-
}));
|
|
334
|
-
});
|
|
335
|
-
llmAgent.on('compaction', (info) => {
|
|
336
|
-
host.emitSessionCompactEvent(sessionId, contextForTools, 'text', info);
|
|
337
|
-
});
|
|
338
|
-
const userMessageWithHistory = buildAgentUserMessage(historyMessages, sessionUserContent);
|
|
339
|
-
let result;
|
|
340
|
-
try {
|
|
341
|
-
await host.emitter.dispatch('ai.agent.start', host.emitter.createPayload(sessionId, contextForTools, 'text', {
|
|
342
|
-
path: 'agent',
|
|
343
|
-
model: chatCandidates[0] || undefined,
|
|
344
|
-
}));
|
|
345
|
-
logPhase(host.phaseConfig, 'agent.run.start', sessionId, { model: chatCandidates[0] || '' });
|
|
346
|
-
result = await runWithBashToolContext(contextForTools, () => llmAgent.run(userMessageWithHistory, []));
|
|
347
|
-
logPhase(host.phaseConfig, 'agent.run.end', sessionId, {
|
|
348
|
-
iterations: result.iterations,
|
|
349
|
-
durationMs: Math.round(now() - tAgent),
|
|
350
|
-
...usageLogFields(result.usage),
|
|
351
|
-
});
|
|
352
|
-
await host.emitter.dispatch('ai.agent.finish', host.emitter.createPayload(sessionId, contextForTools, 'text', {
|
|
353
|
-
path: 'agent',
|
|
354
|
-
model: result.model ?? (chatCandidates[0] || undefined),
|
|
355
|
-
iterations: result.iterations,
|
|
356
|
-
}));
|
|
357
|
-
}
|
|
358
|
-
catch (error) {
|
|
359
|
-
await host.emitter.dispatch('ai.processing.error', host.emitter.createPayload(sessionId, contextForTools, 'text', {
|
|
360
|
-
path: 'agent',
|
|
361
|
-
error: error instanceof Error ? error.message : String(error),
|
|
362
|
-
}));
|
|
363
|
-
throw error;
|
|
364
|
-
}
|
|
365
|
-
finally {
|
|
366
|
-
llmAgent.dispose();
|
|
367
|
-
}
|
|
368
|
-
agentToolCalls = result.toolCalls;
|
|
369
|
-
const spawnedSubagent = result.toolCalls.some(tc => tc.tool === 'spawn_task');
|
|
370
|
-
if (spawnedSubagent) {
|
|
371
|
-
await host.turnTracker.waitForPendingSubagents();
|
|
372
|
-
}
|
|
373
|
-
const delegatedOnly = spawnedSubagent
|
|
374
|
-
&& !result.toolCalls.some(tc => tc.tool === 'run_deferred_task')
|
|
375
|
-
&& !result.toolCalls.some(tc => tc.tool === 'generate_image'
|
|
376
|
-
&& tc.result
|
|
377
|
-
&& typeof tc.result === 'object'
|
|
378
|
-
&& typeof tc.result.image === 'string');
|
|
379
|
-
if (delegatedOnly) {
|
|
380
|
-
reply = '';
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
reply = sanitizeAssistantReply(result.content, {
|
|
384
|
-
toolSummary: formatToolCallsForUser(result.toolCalls),
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
await host.emitter.dispatch('ai.response', host.emitter.createPayload(sessionId, contextForTools, 'text', {
|
|
388
|
-
path: 'agent',
|
|
389
|
-
model: result.model ?? (chatCandidates[0] || undefined),
|
|
390
|
-
iterations: result.iterations,
|
|
391
|
-
reply,
|
|
392
|
-
}));
|
|
393
|
-
logger.debug(formatCompact({
|
|
394
|
-
agent_answer: truncatePreview(reply, 480),
|
|
395
|
-
tool_calls: result.toolCalls.length,
|
|
396
|
-
...(result.toolCalls.length
|
|
397
|
-
? { tools: result.toolCalls.map(tc => tc.tool).join(',') }
|
|
398
|
-
: {}),
|
|
399
|
-
}));
|
|
400
|
-
for (const tc of result.toolCalls) {
|
|
401
|
-
const preview = toolCallResultDebugPreview(tc);
|
|
402
|
-
logger.debug(formatCompact({
|
|
403
|
-
tool_result: tc.tool,
|
|
404
|
-
preview: truncatePreview(preview, 480),
|
|
405
|
-
}));
|
|
406
|
-
}
|
|
407
|
-
logger.debug(`[Agent 路径] 过滤=${filterMs}ms, 记忆=${memMs}ms, Agent=${(now() - tAgent).toFixed(0)}ms, 总=${(now() - t0).toFixed(0)}ms`);
|
|
408
|
-
await host.finalizeActiveTurn({
|
|
409
|
-
usage: result.usage,
|
|
410
|
-
path: 'agent',
|
|
411
|
-
iterations: result.iterations,
|
|
412
|
-
model: result.model ?? (chatCandidates[0] || undefined),
|
|
413
|
-
});
|
|
414
|
-
}
|
|
164
|
+
}),
|
|
165
|
+
});
|
|
166
|
+
const reply = loopResult.reply;
|
|
167
|
+
await host.emitter.dispatch('ai.response', host.emitter.createPayload(sessionId, context, 'text', {
|
|
168
|
+
path: loopResult.path,
|
|
169
|
+
model: loopResult.model,
|
|
170
|
+
iterations: loopResult.iterations,
|
|
171
|
+
reply,
|
|
172
|
+
}));
|
|
415
173
|
await touchSession(sessionDeps(host), sessionId);
|
|
416
174
|
if (isNewSession) {
|
|
417
175
|
host.emitSessionNewEvent(sessionId, context, 'text', sessionUserContent, reply);
|
|
418
176
|
}
|
|
177
|
+
await host.finalizeActiveTurn({
|
|
178
|
+
usage: loopResult.usage,
|
|
179
|
+
path: loopResult.path,
|
|
180
|
+
iterations: loopResult.iterations,
|
|
181
|
+
model: loopResult.model,
|
|
182
|
+
});
|
|
419
183
|
triggerAIHook(createAIHookEvent('message', 'sent', sessionId, {
|
|
420
184
|
userId,
|
|
421
185
|
content: reply,
|
|
422
186
|
platform: platform || '',
|
|
423
187
|
})).catch(() => { });
|
|
424
188
|
await host.emitter.dispatch('ai.processing.finish', host.emitter.createPayload(sessionId, context, 'text', {
|
|
425
|
-
path:
|
|
189
|
+
path: loopResult.path,
|
|
190
|
+
model: loopResult.model,
|
|
426
191
|
reply,
|
|
427
192
|
}));
|
|
428
|
-
logPhase(host.phaseConfig, 'turn.end', sessionId, { path: toolRun.mode === 'pre-exec-fast-path' ? 'fast' : 'agent' });
|
|
429
193
|
host.emitter.emit('ai.typing.stop', host.emitter.createPayload(sessionId, context, 'text', {
|
|
430
194
|
reason: 'processing_complete',
|
|
431
195
|
}));
|
|
432
|
-
|
|
196
|
+
logPhase(host.phaseConfig, 'turn.end', sessionId, {
|
|
197
|
+
path: loopResult.path,
|
|
198
|
+
filter_ms: filterMs,
|
|
199
|
+
total_ms: Math.round(now() - t0),
|
|
200
|
+
});
|
|
201
|
+
const outbound = mergeToolOutboundElements(parseOutput(reply), loopResult.toolCalls);
|
|
433
202
|
if (!reply.trim() && outbound.length === 0)
|
|
434
203
|
return [];
|
|
435
204
|
return outbound;
|
|
436
205
|
}
|
|
437
|
-
|
|
438
|
-
if (tc.tool === 'generate_image' && tc.result && typeof tc.result === 'object' && !Array.isArray(tc.result)) {
|
|
439
|
-
const image = tc.result.image;
|
|
440
|
-
const len = typeof image === 'string' ? image.length : 0;
|
|
441
|
-
return len > 0 ? `[image generated, ${len} b64 chars]` : '[generate_image: no image field]';
|
|
442
|
-
}
|
|
443
|
-
if (tc.tool === 'voice_tts' && tc.result && typeof tc.result === 'object' && !Array.isArray(tc.result)) {
|
|
444
|
-
const audio = tc.result.audio;
|
|
445
|
-
const len = typeof audio === 'string' ? audio.length : 0;
|
|
446
|
-
return len > 0 ? `[audio generated, ${len} b64 chars]` : '[voice_tts: no audio field]';
|
|
447
|
-
}
|
|
448
|
-
return typeof tc.result === 'string' ? tc.result : JSON.stringify(tc.result);
|
|
449
|
-
}
|
|
206
|
+
/** @deprecated 仅保留类型引用;多模态 turn 已统一 agentLoop */
|
|
450
207
|
export async function processMultimodalTurn(host, parts, context, onChunk) {
|
|
451
|
-
const { senderId,
|
|
208
|
+
const { senderId, platform, botId } = context;
|
|
452
209
|
const userId = senderId || 'unknown';
|
|
453
210
|
const sessionKey = resolveIMSessionIdFromToolContext({
|
|
454
211
|
platform,
|
|
455
212
|
botId,
|
|
456
213
|
scope: context.scope,
|
|
457
|
-
sceneId,
|
|
214
|
+
sceneId: context.sceneId,
|
|
458
215
|
senderId,
|
|
459
216
|
});
|
|
460
|
-
const isNewSession = await resolveSessionIsNewBeforeCreate(sessionDeps(host), sessionKey
|
|
217
|
+
const isNewSession = await resolveSessionIsNewBeforeCreate(sessionDeps(host), sessionKey);
|
|
461
218
|
await host.waitForMemoryPersistence();
|
|
462
219
|
const { sessionId } = await beginTurnSession(host, context);
|
|
463
220
|
await host.emitter.dispatch('ai.processing.start', host.emitter.createPayload(sessionId, context, 'multimodal'));
|
|
@@ -475,54 +232,11 @@ export async function processMultimodalTurn(host, parts, context, onChunk) {
|
|
|
475
232
|
reason: 'processing',
|
|
476
233
|
}));
|
|
477
234
|
host.beginActiveTurn();
|
|
478
|
-
const textFragmentsEarly = [];
|
|
479
|
-
for (const p of parts) {
|
|
480
|
-
if (p.type === 'text')
|
|
481
|
-
textFragmentsEarly.push(p.text);
|
|
482
|
-
}
|
|
483
|
-
const textContentEarly = textFragmentsEarly.join(' ') || '[多模态消息]';
|
|
484
|
-
const sessionUserContentEarly = formatUserContentForSession(context, textContentEarly);
|
|
485
|
-
const rawHistoryMessages = await buildHistoryMessages(sessionDeps(host), sessionId, context, sessionUserContentEarly);
|
|
486
|
-
const profileSummary = await host.userProfiles.buildProfileSummary(userId);
|
|
487
|
-
const personaEnhanced = host.buildDisciplinedPrompt(buildEnhancedPersona(host.config, profileSummary, ''));
|
|
488
|
-
const textFragments = [];
|
|
489
|
-
const llmParts = [];
|
|
490
235
|
const supportsVision = providerSupportsVision(host.getTurnProvider());
|
|
491
|
-
|
|
492
|
-
switch (p.type) {
|
|
493
|
-
case 'text':
|
|
494
|
-
textFragments.push(p.text);
|
|
495
|
-
llmParts.push(p);
|
|
496
|
-
break;
|
|
497
|
-
case 'image_url':
|
|
498
|
-
textFragments.push('[图片]');
|
|
499
|
-
if (supportsVision)
|
|
500
|
-
llmParts.push(p);
|
|
501
|
-
else
|
|
502
|
-
llmParts.push({ type: 'text', text: '[用户发送了一张图片]' });
|
|
503
|
-
break;
|
|
504
|
-
case 'video_url':
|
|
505
|
-
textFragments.push('[视频]');
|
|
506
|
-
llmParts.push({ type: 'text', text: `[用户发送了一个视频: ${p.video_url.url}]` });
|
|
507
|
-
break;
|
|
508
|
-
case 'audio':
|
|
509
|
-
textFragments.push('[音频]');
|
|
510
|
-
if (supportsVision)
|
|
511
|
-
llmParts.push(p);
|
|
512
|
-
else
|
|
513
|
-
llmParts.push({ type: 'text', text: '[用户发送了一段音频]' });
|
|
514
|
-
break;
|
|
515
|
-
case 'face':
|
|
516
|
-
textFragments.push(p.face.text || `[表情:${p.face.id}]`);
|
|
517
|
-
llmParts.push({
|
|
518
|
-
type: 'text',
|
|
519
|
-
text: p.face.text ? `[表情: ${p.face.text}]` : `[表情ID: ${p.face.id}]`,
|
|
520
|
-
});
|
|
521
|
-
break;
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
const textContent = textFragments.join(' ') || '[多模态消息]';
|
|
236
|
+
const textContent = summarizeMultimodalParts(parts, supportsVision);
|
|
525
237
|
const sessionUserContent = formatUserContentForSession(context, textContent);
|
|
238
|
+
const profileSummary = await host.userProfiles.buildProfileSummary(userId);
|
|
239
|
+
const personaEnhanced = host.buildDisciplinedPrompt(buildEnhancedPersona(host.config, profileSummary, ''));
|
|
526
240
|
const visionSystemPrompt = await buildMultimodalVisionSystemPrompt(host, {
|
|
527
241
|
context,
|
|
528
242
|
sessionId,
|
|
@@ -530,81 +244,43 @@ export async function processMultimodalTurn(host, parts, context, onChunk) {
|
|
|
530
244
|
personaEnhanced,
|
|
531
245
|
});
|
|
532
246
|
const visionCandidates = resolveModelCandidates(host.getTurnProvider().models, host.modelRegistry, host.getTurnProvider().name, host.config, 'vision');
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
247
|
+
logPhase(host.phaseConfig, 'path.agent_loop', sessionId, { mode: 'multimodal' });
|
|
248
|
+
const userMessage = await buildVisionUserMessage(sessionUserContent, parts, supportsVision, DEFAULT_MULTIMODAL_CONFIG.maxFileBytes);
|
|
249
|
+
let loopResult;
|
|
250
|
+
try {
|
|
251
|
+
loopResult = await host.promptController.schedule({
|
|
252
|
+
sessionKey,
|
|
253
|
+
sessionId,
|
|
254
|
+
userMessages: [userMessage],
|
|
255
|
+
context,
|
|
256
|
+
onChunk,
|
|
257
|
+
execute: (initialMessages, hooks, signal, _turnId) => runAgentLoopVisionTurn({
|
|
258
|
+
host,
|
|
259
|
+
sessionId,
|
|
260
|
+
context,
|
|
261
|
+
visionSystemPrompt,
|
|
262
|
+
userMessages: initialMessages,
|
|
263
|
+
modelCandidates: visionCandidates,
|
|
264
|
+
onChunk,
|
|
265
|
+
promptHooks: hooks,
|
|
266
|
+
signal,
|
|
267
|
+
}),
|
|
268
|
+
});
|
|
542
269
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
const visionModel = visionCandidates[i];
|
|
553
|
-
usedVisionModel = visionModel;
|
|
554
|
-
try {
|
|
555
|
-
reply = '';
|
|
556
|
-
for await (const chunk of host.getTurnProvider().chatStream({ model: visionModel, messages })) {
|
|
557
|
-
if (chunk.usage)
|
|
558
|
-
lastUsage = chunk.usage;
|
|
559
|
-
const delta = chunk.choices?.[0]?.delta;
|
|
560
|
-
if (!delta)
|
|
561
|
-
continue;
|
|
562
|
-
const text = typeof delta.content === 'string' ? delta.content : '';
|
|
563
|
-
if (text) {
|
|
564
|
-
reply += text;
|
|
565
|
-
if (onChunk)
|
|
566
|
-
onChunk(text, reply);
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
reply = stripThinkBlocks(reply);
|
|
570
|
-
if (!reply) {
|
|
571
|
-
logger.warn(formatCompact({ mode: 'multimodal', fallback: visionModel, reason: 'empty_stream' }));
|
|
572
|
-
const response = await host.getTurnProvider().chat({ model: visionModel, messages });
|
|
573
|
-
if (response.usage)
|
|
574
|
-
lastUsage = response.usage;
|
|
575
|
-
const msg = response.choices[0]?.message?.content;
|
|
576
|
-
reply = stripThinkBlocks(typeof msg === 'string' ? msg : '');
|
|
577
|
-
}
|
|
578
|
-
if (reply)
|
|
579
|
-
break;
|
|
580
|
-
}
|
|
581
|
-
catch (err) {
|
|
582
|
-
const isLast = i === visionCandidates.length - 1;
|
|
583
|
-
if (isLast) {
|
|
584
|
-
try {
|
|
585
|
-
const response = await host.getTurnProvider().chat({ model: visionModel, messages });
|
|
586
|
-
if (response.usage)
|
|
587
|
-
lastUsage = response.usage;
|
|
588
|
-
const msg = response.choices[0]?.message?.content;
|
|
589
|
-
reply = stripThinkBlocks(typeof msg === 'string' ? msg : '');
|
|
590
|
-
}
|
|
591
|
-
catch { /* all candidates exhausted */ }
|
|
592
|
-
}
|
|
593
|
-
else {
|
|
594
|
-
logger.warn(formatCompact({
|
|
595
|
-
mode: 'multimodal',
|
|
596
|
-
fallback: `${visionModel}→${visionCandidates[i + 1]}`,
|
|
597
|
-
error: truncatePreview(err.message),
|
|
598
|
-
}));
|
|
599
|
-
}
|
|
600
|
-
}
|
|
270
|
+
catch {
|
|
271
|
+
loopResult = {
|
|
272
|
+
reply: '抱歉,我无法理解这条消息。',
|
|
273
|
+
usage: EMPTY_USAGE,
|
|
274
|
+
path: 'multimodal',
|
|
275
|
+
iterations: 0,
|
|
276
|
+
model: visionCandidates[0] || '',
|
|
277
|
+
toolCalls: [],
|
|
278
|
+
};
|
|
601
279
|
}
|
|
602
|
-
|
|
603
|
-
reply = '抱歉,我无法理解这条消息。';
|
|
604
|
-
reply = sanitizeAssistantReply(reply);
|
|
280
|
+
const reply = loopResult.reply;
|
|
605
281
|
await host.emitter.dispatch('ai.response', host.emitter.createPayload(sessionId, context, 'multimodal', {
|
|
606
282
|
path: 'multimodal',
|
|
607
|
-
model:
|
|
283
|
+
model: loopResult.model,
|
|
608
284
|
reply,
|
|
609
285
|
}));
|
|
610
286
|
await touchSession(sessionDeps(host), sessionId);
|
|
@@ -612,13 +288,14 @@ export async function processMultimodalTurn(host, parts, context, onChunk) {
|
|
|
612
288
|
host.emitSessionNewEvent(sessionId, context, 'multimodal', sessionUserContent, reply);
|
|
613
289
|
}
|
|
614
290
|
await host.finalizeActiveTurn({
|
|
615
|
-
usage:
|
|
291
|
+
usage: loopResult.usage,
|
|
616
292
|
path: 'multimodal',
|
|
617
|
-
model:
|
|
293
|
+
model: loopResult.model,
|
|
294
|
+
iterations: loopResult.iterations,
|
|
618
295
|
});
|
|
619
296
|
await host.emitter.dispatch('ai.processing.finish', host.emitter.createPayload(sessionId, context, 'multimodal', {
|
|
620
297
|
path: 'multimodal',
|
|
621
|
-
model:
|
|
298
|
+
model: loopResult.model,
|
|
622
299
|
reply,
|
|
623
300
|
}));
|
|
624
301
|
host.emitter.emit('ai.typing.stop', host.emitter.createPayload(sessionId, context, 'multimodal', {
|